1

I am writing a Django application and need to update a model with an AJAX request, which will only contain a subset of the model's fields as keys. So if I have the model

class TheModel(models.Model):
    a = models.CharField(max_length=16)
    b = models.IntegerField()
    c = models.TextField()
    d = models.ManyToManyField(AnotherModel)

then I could get requests like

id=7&a=Hello
id=7&a=Test&b=123
id=13&b=14&c=Description&d=6&d=10

that is, I will always get the ID field but any subset of the others.

I can't find a "nice" way to do this in Django 1.5: at first I tried

instance = get_instance_or_404(request["id"])
data = django.forms.models.model_to_dict(instance)
data.update(request.POST)
form = TheModelForm(data, instance=instance)
if form.is_valid():
   form.save()
   ...
else:
   ...

but this doesn't seem to work well with the m2m field, and moreover model_to_dict feels incredibly ugly to me. So I also did

instance = get_instance_or_404(request["id"])
for k in TheModel._meta.fields:
    if k in request:
        setattr(instance, k, request[k])
try:
    instance.full_clean()
except ValidationError as e:
    ...
instance.save()

but I don't exactly understand how to handle the m2m fields here either.

Is there an idiomatic way to do this in Django? Thanks in advance for your help.

Actorclavilis
  • 953
  • 7
  • 17
  • m2m fields are updated by `model.m2m_field.add()` and `model.m2m_field.remove()` both of those functions take an arg that is the instance of the item you are adding or removing. – Jacob Valenta Jul 21 '13 at 21:10
  • Thanks for the useful tip, but I'm really looking for a way to do this that's as nice and instantly recognizable as creating a model instance with all of the data from ModelForm. If nothing else I'd like to be able to add to the m2m_field by primary key rather than by instance to save a lot of boilerplate. – Actorclavilis Jul 21 '13 at 22:38

1 Answers1

1

First of all, GET requests should never update data. They should only ever read and display data. The HTTP method you need to use is POST. This answer is worth a read.

Now that's out of the way, the best way to achieve what you want is by using the generic UpdateView. Here's some sample code:

# urls.py
from views import UpdateTheModelView

urlpatterns = patterns('',
                       url(r'^update-TheModel/(?P<pk>\d+)/?',
                       UpdateTheModelView.as_view(),
                       name='update_the_model'),
)


# forms.py
from django import forms
from models import TheModel

class TheModelForm(forms.ModelForm):
    class Meta:
        model = TheModel
        fields = ('a', 'b', 'c', 'd',)


# views.py
from django.core.urlresolvers import reverse
from django.views.generic import UpdateView
from models import TheModel
from forms import TheModelForm

class UpdateTheModelView(UpdateView):
    model = TheModel
    form_class = TheModelForm
    template_name = 'themodel_form.html'

    def get_success_url(self):
        """
        Just here to redirect back to the update page when the form is posted
        """
        return reverse('update_the_model', args=[self.object.id, ])

And a simple example template to display the form at yourapp/templates/themodel_form.html:

<form action="." method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <input type="submit" value="Update" />
</form>

So now if you have an instance of TheModel saved with an id of 1, you can view the update form loaded with initial data by going to http://your-website.co.uk/update-TheModel/1/ and you can update it by clicking Update or by sending a POST request to this page's URL (along with the CSRF token), which can easily be done in the background with jQuery or vanilla Javascript.

Community
  • 1
  • 1
Matt Deacalion
  • 5,692
  • 2
  • 20
  • 50
  • Thanks, but this is not quite what I need. Specifically, this form is never shown to any user (in fact, it's not even rendered in HTML; this is an HTTP interface between the server and a client application). Moreover, if I POST only some fields to that View, then form validation fails, whereas I want it to use the already-existing values from the DB. – Actorclavilis Jul 23 '13 at 22:13
  • It's not exactly what you need, no. With a little tweaking however it can be just that. You can override the `get` method on UpdateView so no HTML get's returned, you can also change the `ModelForm` to make certain fields optional. – Matt Deacalion Jul 24 '13 at 14:04