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`?

Original source