[Fixed]-Django update one field using ModelForm


You could use a subset of the fields in your ModelForm by specifying those fields as follows:

class PartialAuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = ('name', 'title')

From the docs:

If you specify fields or exclude when creating a form with ModelForm,
then the fields that are not in the resulting form will not be set by
the form’s save() method.



Got this figured. What I do is update the request.POST dictionary with values from the instance – so that all unchanged fields are automatically present. This will do it:

from django.forms.models import model_to_dict
from copy import copy

def UPOST(post, obj):
    '''Updates request's POST dictionary with values from object, for update purposes'''
    post = copy(post)
    for k,v in model_to_dict(obj).iteritems():
        if k not in post: post[k] = v
    return post

and then you just do:

form = CModelForm(UPOST(request.POST,c_instance),instance=c_instance)


I’m surprised nobody mentioned modelform_factory yet.

You can easily achieve partial form updates by creating a specific version of a form class, defining the subset of fields you’re interested in:

from django.forms import modelform_factory


foo = get_object_or_404(Foo, slug=foo_slug)

if request.method == "POST": 
    PartialFooForm = modelform_factory(
        Foo, form=FooForm, fields=("my_first_field", "my_second_field",)
    form = PartialFooForm(request.POST, instance=foo)
    if form.is_valid():
        foo = form.save()
        return redirect(foo)
    form = FooForm(instance=foo)


Django’s ModelForm isn’t designed to handle a partial update of an arbitrary subset of fields on the model.

The use case described by the OP, a desktop app hitting an API, would be handled better by Django Rest Framework.

Specifically, you would create a serializer that inherits from ModelSerializer, and then use it in an UpdateAPIView. Serializers in DRF are analogous to forms in Django.

If you don’t mind adding another dependency with DRF, which is a really great library, this probably beats rolling your own solution.

class MyModelSerializer(serializers.ModelSerializer):

    class Meta:
        model = MyModel
        fields = '__all__'

class MyModelDetail(generics.RetrieveUpdateAPIView):
    queryset = MyModel.objects.all()
    serializer_class = MyModelSerializer


I know I’m very late to the party, but could you create a factory function for the form.

def model_form_factory(cls, data, *args, **kwargs):
    """ Create a form to validate just the fields passed in the data dictionary.

    e.g. form = form_factory(MyModel, request.POST, ...)


    data = data.copy()
    data.pop('csrfmiddlewaretoken', None)

    class PartialForm(forms.ModelForm):
        class Meta:
            model  = cls
            fields = data.keys()

    return PartialForm(data, *args, **kwargs)


I solved this similar to Roman Semko’s answer (which may not work with ManyToMany fields):

Alter your form’s __init__ method to update the data:

import urllib

from django import forms
from django.http import QueryDict
from django.forms.models import model_to_dict

class MyModelForm (forms.ModelForm):
    class Meta:
        model = MyModel

    def __init__ (self, *args, **kwargs):
        super(MyModelForm, self).__init__(*args, **kwargs)

        # if form has being submitted and 
        # model instance exists, then get data

        if self.is_bound and self.instance.pk:

            # get current model values
            modeldict = model_to_dict( instance )
            modeldict.update( self.data.dict() )

            # add instance values to data
            urlencoded = urllib.urlencode( modeldict )
            self.data = QueryDict( urlencoded )


Well, you must create method to init form with absent data in your request, something like:

class MyModel(Model):
    ... your model ...

    def initial(self,data):
        fields = [list of possible fields here]

        for field in fields:
            if data.get(field) is None:
                data[field] = getattr(self,field)

        return data

Then, pass this data in your form like:

form = MyForm(instance.initial(request.POST.copy()),instance=instance)

for JSON:

from json import loads

form = MyForm(instance.initial(loads(request.body)),instance=instance)

Leave a comment