I have a ModelForm that needs render specific fields that should be excluded for validation.
First thing I did was remove fields from Meta.fields. Those excluded fields are already defined declaratively as fields. Second, I injected instance value to initial property of each of those exclude fields.
class Form(forms.ModelForm):
field1 = ...
field2 = ...
....
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance.pk:
self.fields["field1"].initial = ...
self.fields["field2"].initial = ...
Rendering works as expected, but modelform tries validation on field1 and field2 even when they are excluded from Meta.fields.
Why is that? How would you render but skip validation for certain fields?
Here’s how I did.
If form is unbound, meaning form has no data passed to form constructor, you can skip validation.
form = Form() # unbound
form = Form({"field1": ...}) # bound
Make form instance with unbound form for rendering, and use validation only for bound form
class Form(forms.ModelForm):
field1 = ...
field2 = ...
....
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not self.is_bound and self.instance.pk:
self.fields["fields1"].initial = ...
self.fields["fields2"].initial = ...
My initial reaction to this is to ask “Why?” What is the specific issue or situation where you believe this to be appropriate? (Questions like this appear to me as a possible XY-Problem.)
That’s how forms normally work. If you’re seeing something else, then you’ve got a mistake somewhere else with how you’re using the form.
Hi.
My situation is, when a model is updated with ModelForm one ModelChoiceField whose queryset has only active ones, which means it excludes soft deleted ones. Let’s call this fk instance department. Imagine a department is soft deleted, meaning its is_active value set to False, but it is still referenced by the model with ModelForm.
class ProductForm(forms.ModelForm):
....
department = forms.ModelChoiceField(queryset=Department.objects.filter(is_active=True, disabled=True)
class Meta:
model = Product
fields = ["department", ...]
Now Product instance has still inactive department. Form is not valid, since department value is invalid choice. So I removed the field from Meta.fields.
fields = [] # removed
Enter a new problem. Now form is valid, but it doesn’t render department value, even when department is active one.
class ProductForm(forms.ModelForm):
department = forms.ModelChoiceField(queryset=Department.objects.filter(is_active=True, disabled=True)
class Meta:
model = Product
fields = []
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs):
if not self.is_bound and self.instance.pk:
self.fields["department"].initial = self.instance.department
form = ProductForm(instance=product) # shows department
form.fields["department"].initial # has value
# here I don't do form.is_valid(). It's GET view anyway.
form = ProductForm(instance=product, data=some_data)
form.is_valid() # form is valid. Here I do validation because It's POST view.
So I came up with the above workaround. My approach solves the render-but-not-validate problem. I thought setting initial value to model form field unspecified in Meta.field would do, be it unbound form or not. But then form validation happens.
class ProductForm(forms.ModelForm):
department = forms.ModelChoiceField(queryset=Department.objects.filter(is_active=True, disabled=True)
class Meta:
model = Product
fields = []
data = {} # department is disabled field anyway. product.department.is_active is False.
form = ProductForm(instance=product, data=data, initial={"department": product.department})
form.is_valid() # still False, even department is not part of Meta.fields?
I would look at this from a different angle. I’d be approaching this with an eye toward making the validation work with the situation you’re facing rather than trying to disable validation - something I always consider a bad idea when dealing with submitted data.
By disabling validation on that field, you will accept any valid value for department, which I’m sure is not what you want.
So the first question is, what is the set of valid submissions for that field?
- Active departments only? (This implies that an FK to an inactive department must be changed when the form is submitted.)
- All departments regardless of status? (The validation queryset on POST would then be different than the queryset on GET)
- Active departments + the currently selected department? (This will change the queryset, but could be the same queryset for both cases.)
Answer is active departments only.
If I allow department to be included fields and to go through validation process, what about disabled? Wouldn’t then empty data gets passed and department be None? This field is not required in form.
I’m not following what you’re asking here, especially since you just said that an active department must be submitted.
If the form must reference an active department, then it seems to me that you would want validation to occur, with the same list of active departments that would be used for the ModelChoiceField.
I should have been more explicit.
This form class has department field that is disabled. When data is sent to update view, department is not included. Modifying department instance is done elsewhere.
Instead this update view only shows which department this product instance has. Problem here is if I include department field for rendering (put department in Meta.fields), in case department is modified elsewhere as inactive(is_active being False), this update view’s form becomes invalid when I send post request to this view even department.idis not in request.POST.
I think when modelform validate fk field(department) it checks if current product.department is in active departments defined in queryset parameter of ModelForm.department field.
In that case, the optimal solution is to not render the department name as a field but as literal text.
The next best option is to define the queryset to return only the current value for that instance.
Because, quoting from Disabled
Even if a user tampers with the field’s value submitted to the server, it will be ignored in favor of the value from the form’s initial data.
So by defining the queryset to return the initial value, it will always be valid.
Other options include changing the field type from a ModelChoiceField to a CharField - avoiding the entire validation issue regarding a queryset.
I think I’ll opt for setting queryset that has initial value since that will be the least code change for now.
Thank you for your help!