Resubmit form with errors and FileField or ImageField

The form validation process is very useful and does not require too much code to be written. But what if you need to save certain fields when the form is not valid?
Let's consider the following models.py, forms.py and situation:
- # models.py
- from django.db import models
-
- class Avatar(modesl.Model):
- original_image = models.ImageField()
-
- class Character(models.Model):
- name = models.TextField()
- avatar = models.ForeignKey(Avatar, blank=True, null=True)
- # forms.py
- from django import forms
- from models import Character
-
- class CharacterForm(forms.ModelForm):
- name = forms.CharField(required=True, min_length = 3)
- avatar = forms.ImageField(required=True)
-
- class Meta:
- model = Character
- fields = ['name', 'avatar']
-
- def clean_avatar(self):
- """
- we need to return a Avatar object to match the model field type
- """
- img = self.cleaned_data.get('avatar')
- if not img:
- raise forms.ValidationError('You must select a character image')
- avatar = Avatar(original_image = img)
- avatar.save()
- return avatar
What will happen to the avatar field if the user inputs a name that is already in use? Its value will get deleted and you risk the user not noticing it, generating another form error page.
What can we do about it?
We don't have access to the session inside the clean method to set avatar.id in it, nor to form.cleaned_data in views.py.
We must use what we can: altering the data used to populate form fields values inside the avatar clean method. So let's add a separate hidden field that will store our avatar.id and we can now make the avatar as not required since it can be empty.
- # forms.py
- class CharacterForm(forms.ModelForm):
- name = forms.CharField(required=True, min_length = 3)
- avatar_id = forms.CharField(required=False, widget=forms.HiddenInput)
- avatar = forms.ImageField(required=False)
Very important: the order of the Meta fields. We want the avatar_id to be in front of avatar:
- # forms.py
- class Meta:
- model = Character
- fields = ['name', 'avatar_id', 'avatar']
This way the avatar_id field is evaluated before we modify its value and we don't risk getting our custom value getting overwritten with POST data.
The avatar_id value is set in the clean_avatar method, because that will still be the only place where we create the avatar.
- # forms.py
- def clean_avatar(self):
- img = self.cleaned_data.get('avatar')
- avatar_id = self.cleaned_data.get('avatar_id')
- # avatar or avatar_id must exist
- if not (img or avatar_id):
- raise forms.ValidationError('You must select a character image')
- if not img and avatar_id:
- try:
- avatar = Avatar.objects.get(id=avatar_id)
- return avatar
- except:
- raise forms.ValidationError('You must select a character image')
-
- avatar = Avatar(original_image = img)
- avatar.save()
-
- if not self.is_valid() and avatar:
- self.data['avatar_id'] = avatar.id
-
- return avata
Of course there are other ways, like adding a JavaScript to check all fields value before submitting the form, but this handles cases where the user disabled or the browser does not support JS.
Category: Django



Leave a Comment :
Leave a Comment