Reputation: 1592
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
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
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