How do I programmatically set fields in ModelForm in Django?
django-forms, python
Solution
Another possible way is generate a inline form class in the view to exclude fields based on the request, for example, define a normal model form for Order model, called OrderDetailForm:
class OrderDetailForm(forms.ModelForm):
class Meta:
model = Order
fields = ('work_type', 'vendor', 'note')
widgets = {
'note': forms.Textarea(),
}
In the view, for example, edit order, create a customized form based on the OrderDetailForm:
def edit(request, order_id):
order = Order.objects.get(pk=order_id)
can_edit_work_type = bool(request.REQUEST.get('can_edit_work_type', False))
can_edit_vender = bool(request.REQUEST.get('can_edit_vender', False))
can_edit_note = bool(request.REQUEST.get('can_edit_note', False))
exclude_fields = []
if not can_edit_work_type:
exclude_fields.append('work_type')
if not can_edit_vender:
exclude_fields.append('vender')
if not can_edit_note:
exclude_fields.append('note')
class CustomizedOrderForm(OrderDetailForm):
class Meta:
model = Order
exclude = tuple(exclude_fields)
if request.method == 'POST':
form = CustomizedOrderForm(instance=order, data=request.POST)
if form.is_valid():
form.save()
else:
form = CustomizedOrderForm(instance=order)
return render(request, 'order_form.html', {'form': form})
Problem
From this question I want to convert my form from regular `Form` to `ModelForm` so I can take advantage of `instance` parameter in `ModelForm`. Here is my current form code: ``` class OrderDetailForm(forms.Form): def __init__( self, user, can_edit_work_type=None, can_edit_vendor=None, can_edit_note=None, *args, **kwargs ): super(OrderDetailForm, self).__init__(*args, **kwargs) if can_edit_work_type: self.fields['work_type'] = forms.ChoiceField(choices=Order.WORK_TYPE_CHOICES) if can_edit_vendor: self.fields['vendor'] = forms.ModelChoiceField( queryset=Vendor.objects.all(), empty_label="Choose a vendor", ) if can_edit_note: self.fields['note'] = forms.CharField(widget=forms.Textarea) def clean(self): super(OrderDetailForm, self).clean() if 'note' in self.cleaned_data: if len(self.cleaned_data['note']) < 50: self._errors['note'] = self.error_class([u"Please enter a longer note."]) del self.cleaned_data['note'] return self.cleaned_data ``` As you can see, I have some `if` statements that determine whether the fields even show you in the forms (logically it means certain users can only edit certain parts of the fields). How would I do that in `ModelForm`? I understand `fields` to be a tuple, so it can't be appended like I did in `Form`. So I want to do something like ``` class OrderDetailForm(forms.ModelForm): class Meta: model = Order # fields = ('work_type', 'vendor', 'note') I can't do that since I need to be able to control it. See below. # Can I control widgets even if that field doesn't exist? widgets = { 'note': forms.Textarea(), } def __init__( self, user, can_edit_work_type=None, can_edit_vendor=None, can_edit_note=None, *args, **kwargs ): super(OrderDetailForm, self).__init__(*args, **kwargs) fields = [] if can_edit_work_type: fields.append('work_type') if can_edit_vendor: fields.append('vendor') if can_edit_note: fields.append('note') self.Meta.fields = tuple(fields) # Does this work? def clean(self): super(OrderDetailForm, self).clean() if 'note' in self.cleaned_data: if len(self.cleaned_data['note']) < 50: self._errors['note'] = self.error_class([u"Please enter a longer note."]) del self.cleaned_data['note'] return self.cleaned_data ``` Is that possible? How do you control the fields in `ModelForm`?