RHSmith159
RHSmith159

Reputation: 1592

ModelForm saving over model data with empty fields

I'm building an Edit form for a model in my database using a ModelForm in Django. Each field in the form is optional as the user may want to only edit one field.

The problem I am having is that when I call save() in the view, any empty fields are being saved over the instance's original values (e.g. if I only enter a new first_name, the last_name and ecf_code fields will save an empty string in the corresponding instance.)

The form:

class EditPlayerForm(forms.ModelForm):

    class Meta:
        model = Player
        fields = ['first_name', 'last_name', 'ecf_code']

    def __init__(self, *args, **kwargs):
        super(EditPlayerForm, self).__init__(*args, **kwargs)
        self.fields['first_name'].required = False
        self.fields['last_name'].required = False
        self.fields['ecf_code'].required = False

The view:

def view(request, player_pk = ''):

    edit_player_form = forms.EditPlayerForm(auto_id="edit_%s")

    if "edit_player_form" in request.POST:

        if not player_pk:

            messages.error(request, "No player pk given.")

        else:

            try:

                selected_player = Player.objects.get(pk = player_pk)

            except Player.DoesNotExist:

                messages.error(request, "The selected player could not be found in the database.")
                return redirect("players:management")

            else:

                edit_player_form = forms.EditPlayerForm(
                    request.POST,
                    instance = selected_player
                )

                if edit_player_form.is_valid():

                    player = edit_player_form.save()
                    messages.success(request, "The changes were made successfully.")
                    return redirect("players:management")

                else:
                    form_errors.convert_form_errors_to_messages(edit_player_form, request)

    return render(
        request,
        "players/playerManagement.html",
        {
            "edit_player_form": edit_player_form,
            "players": Player.objects.all(),
        }
    )

I've tried overriding the save() method of the form to explicitly check which fields have values in the POST request but that didn't seem to make any difference either.

Attempt at overriding the save method:

def save(self, commit = True):

    # Tried this way to get instance as well
    # instance = super(EditPlayerForm, self).save(commit = False)

    self.cleaned_data = dict([ (k,v) for k,v in self.cleaned_data.items() if v != "" ])

    try:
        self.instance.first_name = self.cleaned_data["first_name"]
    except KeyError:
        pass

    try:
        self.instance.last_name = self.cleaned_data["last_name"]
    except KeyError:
        pass

    try:
        self.instance.ecf_code = self.cleaned_data["ecf_code"]
    except KeyError:
        pass


    if commit:
        self.instance.save()


    return self.instance

I also do not have any default values for the Player model as the docs say the ModeForm will use these for values absent in the form submission.

EDIT:

Here is the whole EditPlayerForm:

class EditPlayerForm(forms.ModelForm):


    class Meta:
        model = Player
        fields = ['first_name', 'last_name', 'ecf_code']

    def __init__(self, *args, **kwargs):
        super(EditPlayerForm, self).__init__(*args, **kwargs)
        self.fields['first_name'].required = False
        self.fields['last_name'].required = False
        self.fields['ecf_code'].required = False


    def save(self, commit = True):

        # If I print instance variables here they've already
        # been updated with the form values

        self.cleaned_data = [ k for k,v in self.cleaned_data.items() if v ]

        self.instance.save(update_fields = self.cleaned_data)

        if commit:

            self.instance.save()

        return self.instance

EDIT:

Ok so here is the solution, I figured I'd put it here as it might be useful to other people (I've certainly learned a bit from this).

So it turns out that the is_valid() method of the model form actually makes the changes to the instance you pass into the form, ready for the save() method to save them. So in order to fix this problem, I extended the clean() method of the form:

 def clean(self):

    if not self.cleaned_data.get("first_name"):
        self.cleaned_data["first_name"] = self.instance.first_name

    if not self.cleaned_data.get("last_name"):
        self.cleaned_data["last_name"] = self.instance.last_name

    if not self.cleaned_data.get("ecf_code"):
        self.cleaned_data["ecf_code"] = self.instance.ecf_code

This basically just checks to see if the fields are empty and if a field is empty, fill it with the existing value from the given instance. clean() gets called before the instance variables are set with the new form values, so this way, any empty fields were actually filled with the corresponding existing instance data.

Upvotes: 2

Views: 1759

Answers (2)

PRMoureu
PRMoureu

Reputation: 13337

You could maybe use the update() method instead of save() or the argument update_field

self.instance.save(update_fields=['fields_to_update'])

by building the list ['fields_to_update'] only with the not empty values.

It should even work with the comprehension you've tried :

self.cleaned_data = [ k for k,v in self.cleaned_data.items() if v ]

self.instance.save(update_fields=self.cleaned_data)

EDIT :

Without overriding the save method (and commenting out this attempt in the form):

not_empty_data = [ k for k,v in edit_player_form.cleaned_data.items() if v ]
print(not_empty_data)
player = edit_player_form.save(update_fields=not_empty_data)

Upvotes: 1

Miguel Ike
Miguel Ike

Reputation: 484

You could check the values if it's not empty in your view without overriding save()

if edit_player_form.is_valid():
    if edit_player_form.cleaned_data["first_name"]:
        selected_player.first_name = edit_player_form.cleaned_data["first_name"]
    if edit_player_form.cleaned_data["last_name"]:
        selected_player.last_name= edit_player_form.cleaned_data["last_name"]
    if edit_player_form.cleaned_data["ecf_code"]:
        selected_player.ecf_code= edit_player_form.cleaned_data["ecf_code"]
    selected_player.save()

This should work fine with what you want. I'm not sure if it's the best way to do it but it should work fine.

Upvotes: 1

Related Questions