Let's say I have the following model
class Foo(models.Model):
title = models.CharField(default='untitled')
# Bar is a MPTT class, so I'm building a tree here
# Should not matter for my question...
root = models.ForeignKey(Bar)
leaf = models.ForeignKey(Bar)
To create new Foo objects I want to make use of a ModelForm like this:
class FooForm(ModelForm):
# possibly custom validation functions needed here...
class Meta:
model = Foo
fields = '__all__'
My view looks like this:
def create(request, leaf_id=None):
form = FooForm(data=request.POST or None)
if form.is_valid():
new = form.save()
return redirect('show.html', root_id=new.root.id)
return render('create_foo.html',
{ 'form': form })
As you can see, the view function should be used to handle two different use-cases:
/foo/create/
/foo/create/4
where 4
is a leaf-ID.If the leaf-ID is given, the form obviously isn't required to show a form field for this. Furthermore, root
can be determined from leaf
, so it isn't required aswell.
I know that I can dynamically change the used widgets, so I can switch them to HiddenInput
, but I would like to not even show them as hidden to the user. But if I dynamically exclude them, they are not available for form validation and the whole process will fail during the validation process.
What I would like to achieve is: Show only form fields to the user, that are not yet pre-filled. Is there any best-practice available for this case?
You can do that by overriding the __init__()
method of FooForm
.
We override the __init__()
method and check if instance
argument was passed to the form. If instance
was passed, we disable the root
and leaf
form fields so that it is not displayed in the template.
We will pass instance
argument to the form when the request is of type foo/create/4
i.e. leaf_id
is not None
.
forms.py
class FooForm(ModelForm):
def __init__(self, *args, **kwargs):
super(FooForm, self).__init__(*args, **kwargs) # call the 'super()' init method
instance = getattr(self, 'instance', None) # get the `instance` form attribute
if instance and instance.id: # check if form has 'instance' attribute set and 'instance' has an id
self.fields['root'].widget.attrs['disabled'] = 'disabled' # disable the 'root' form field
self.fields['leaf'].widget.attrs['disabled'] = 'disabled' # disable the 'leaf' form field
# custom validation functions here
....
class Meta:
model = Foo
fields = '__all__'
In our view, we first check if leaf_id
argument was passed to this view. If leaf_id
was passed,we retrieve the Foo
object having leaf id as the leaf_id
. This instance
is then passed when initializing a form and is updated when form.save()
is called. We will use the instance
to populate the form with values as the attributes set on the instance
.
If leaf_id
is not passed, then we initialize FooForm
with data
argument.
views.py
def create(request, leaf_id=None):
# Get the instance if any
instance = None
if leaf_id:
instance = Foo.objects.get(leaf_id=leaf_id) # get the 'Foo' instance from leaf_id
# POST request handling
if request.method=='POST':
if instance:
form = FooForm(data=request.POST, instance=instance) # Populate the form with initial data and supply the 'instance' to be used in 'form.save()'
else:
form = FooForm(data=request.POST)
if form.is_valid():
new = form.save()
return redirect('show.html', root_id=new.root.id)
return render('create_foo.html',
{ 'form': form })
# GET request handling
if instance:
form = FooForm(initial=instance._data, instance=instance) # form will be populated with instance data
else:
form = FooForm() # blank form is initialized
return render('create_foo.html',
{ 'form': form })