Django: ModelForm with conditions
I am trying to create a form variable. By default, the player has level 0 and can simply change the name. Later, when he is level 1, he can change his name and avatar. When he is at the third level, he can change his name, avatar and job. Etc ...
Models.py:
class Player(models.Model):
level = models.SmallIntegerField(default=0)
name = models.CharField(max_length=50)
avatar = models.URLField(default='http://default-picture.com/01.png')
job = models.TextField(null=True)
Fomrs.py:
class ProfileForm(forms.ModelForm):
class Meta:
model = Player
fields = ['name', 'avatar', 'job']
widgets = {
'name': forms.TextInput(),
'avatar': forms.TextInput(),
'job': forms.Textarea(),
}
Views.py:
def game(request, id):
user = get_object_or_404(Player, id=id)
if request.method == 'POST':
form = ProfileForm(request.POST, instance=user)
if form.is_valid():
form.save()
return HttpResponse('Success')
else:
form = ProfileForm(instance=user)
return render(request, "page/template.html",
{'form': form})
template.html:
{{ form }}
Can I add a condition to render a form before submitting to render render? Or do I need to do this in my conditional template?
I just want the instantiated object to have more or less positive elements in terms of one of these parameters (player level as an example).
source to share
You can overwrite the form method __init__
to conditionally remove or disable fields:
class ProfileForm(forms.ModelForm):
...
def __init__(self, *args, **kwargs):
super(ProfileForm, self).__init__(*args, **kwargs)
if self.instance and self.instance.level < 3:
self.fields['job'].disabled = True # still displays the field in the template
# del self.fields['job'] # removes field from form and template
if self.instance and self.instance.level < 1:
self.fields['avatar'].disabled = True
source to share
Django pre_save signal can be used to solve this problem. Before you save the model is called hook
on pre_save
. You can write conditions to check if the user is allowed to change fields.
You will also need to save a copy of the model to compare the state in pre_save
, this can be done using the post_init hook.
from django.dispatch import receiver , Signal
from django.db.models.signals import post_init , pre_save
@receiver(pre_init , sender = Player)
def cache(sender , instance , **kwargs):
instance.__original_name = instance.name
instance.__original_avatar = instance.avatar
instance.__original_job = instance.job
@receiver(pre_save , sender= Player)
def check_update(sender , instance , **kwargs):
if instance.level == 1:
#Revert changes to avatar and job, and keep changes in the name
if instance.level == 2:
#Revert changes to job , and keep changes in the name and avatar
if instance.level == 3:
#Keep all changes
This way you can keep track of all update fields.
source to share
You can do this customization of your form in init method
In the situations described earlier, you can do the following:
class ProfileForm(forms.ModelForm):
class Meta:
model = Player
fields = ['name', 'avatar', 'job']
widgets = {
'name': forms.TextInput(),
'avatar': forms.TextInput(),
'job': forms.Textarea(),
}
def __init__(self, *args, **kwargs):
super(ProfileForm, self).__init__(*args, **kwargs)
# checking if an instance exist
if self.instance.id:
# then at this point you can set as read only the fields about each case
if self.instance.level < 1:
self.fields["avatar"].widget.attrs["readonly"] = True
self.fields["job"].widget.attrs["readonly"] = True
elif self.instance.level >= 1 and self.instance.level < 3:
self.fields["job"].widget.attrs["readonly"] = True
source to share