Using TastyPie to update a ForeignKey field to null
Asked Answered
S

4

6

Is it possible to use TastyPie to update a ForeignKey field to None?

Related question: tastypie won't remove foreignkey reference during PUT

What I have:

class SomeObject(models.Model):
    some_field = models.ForeignKey(SomeOtherObject, null=True)

(and of course the corresponding TastyPie resource class that works fine for updating other fields)

What I want:

To update some_field to None using TastyPie.

What I've tried (in JavaScript):

$.put('/my/api/model/someobject/42/', { some_field: null });
$.put('/my/api/model/someobject/42/', { some_field: '/my/api/model/someotherobject/null/' });
$.put('/my/api/model/someobject/42/', { some_field: '' });
$.put('/my/api/model/someobject/42/', { some_field: 0 });
$.put('/my/api/model/someobject/42/', { some_field: false });

And so on. These all result in 404 or 400. Some result in 204, but the database is not updated.

Reading through the code in full_dehydrate(), it seems it might not be possible at present do so.

I've looked at the recent code on github, and I'm not convinced this is possible.

Saccharometer answered 14/3, 2012 at 4:9 Comment(2)
As a workaround, I have written a custom obj_update() method on my resource class, wherein I check if 'some_field' in bundle.data and bundle.data['some_field'] is None:, at which point I set the object's some_field to None and save() it. In my JavaScript, I pass { some_field: null }Saccharometer
Although I really like this solution, for me it does not work when an entry is already made in my database. In that case it simply loads the existing data and the statement behind the if is not True (while it should be)... Anyone found a solution for that?Crying
H
9

Here is a generic solution. Create a base resource and extend all other resources from this one. It's a hack, but it works.

class MyResource(ModelResource):
    def obj_update(self, bundle, request=None, **kwargs):
        for field_name in self.fields:
            field = self.fields[field_name]

            if type(field) is ToOneField and field.null and bundle.data[field_name] is None:
                setattr(bundle.obj, field_name, None)

        return super(MyResource, self).obj_update(bundle, **kwargs)
Harneen answered 26/3, 2012 at 14:34 Comment(6)
Thanks for a working solution, something like this definitely needs to be a part of Tastypie itself.Excursive
This is nearly what I came up with, but has the added advantage of being more broadly applicable by checking the type(field). Great answer.Saccharometer
A bit O/T here: PEP8 says "Comparisons to singletons like None should always be done with 'is' or 'is not', never the equality operators." And it's not only just a convention :) Also == True is redundant.Laudable
Thanks minder, fixed. Also I moved the save() out the loop, only need to do that once.Harneen
Great answer, but your code saves the obj twice -- once in the super call, and once in the save() call. You can avoid this by removing your last two lines and just returning the super call. I'll offer an edit that does this.Sympathizer
I'm using NamespacedModelResource, will that work too?Crying
S
2

Just add a hydrate method for the field:

def hydrate_some_field(self, bundle):
    some_object = bundle.obj
    some_other_object = bundle.data['some_field']

    if some_other_object == '':
        some_object.some_field = None
        del bundle.data['some_field']

    return bundle

Good luck!

Sympathizer answered 14/2, 2014 at 17:55 Comment(3)
This seems to be more appropriate solution for a given task. Thanks.Sackey
By the way, you should return a new field value from the per-field hydrate method. As documentation says "The return value is put in the bundle.obj attribute for that fieldname."Sackey
I'll have to look closer at that. All my APIs return bundle in the per-field hydrate methods. Hmmm....Sympathizer
C
0

For me the answer of Adam Thomas was not working. I had to change the if condition.

So the total code becomes:

def obj_update(self, bundle, request=None, **kwargs):
    super(MyResource, self).obj_update(bundle, **kwargs)
    field_to_update=[]
    for field_name in self.fields:
        field = self.fields[field_name]
        if field.null and (field_name in request.POST):
            if request.POST[field_name] is u'':
                setattr(bundle.obj, field_name, None)
                field_to_update.append(field_name)
    bundle.obj.save(update_fields=field_to_update)
    return bundle

(notice: I used the update_field to make the sql query a lot more nicer :))

(btw: this is also working for namespacedmodelresources :))

(I could not get it working with the return super call, then it did not save for me)

Crying answered 9/10, 2014 at 5:5 Comment(0)
L
-1

Try this:

$.put('/my/api/model/someobject/42/', { some_field: 'None' }); //DOES NOT WORK

I had a similar issue where I was trying to filter some objects on a null foreign key and was able to get them with a GET to:

http://localhost:8000/api/v1/page/?format=json&next_page=None

Update:

While I was able to GET matching objects by passing 'None', it does not appear to work for PUT. I was able to update a foreign key field to None with this call:

$.ajax('/api/v1/page/1/',{
    contentType: 'application/json', 
    type: 'put', 
    data: JSON.stringify({"next_page": null})
});

I guess this skirts the issue by passing the null as application/json. It's likely possible to pass a null as application/x-www-form-urlencoded, but I don't know how.

Incidentally, application/json is the encoding tastypie expects by default, whereas application/x-www-form-urlencoded is the encoding jquery and some other (all?) libraries use for ajax. Maybe tastypie's default json encoding is easier to work with.

Lesseps answered 15/3, 2012 at 22:47 Comment(4)
Doesn't work for me. This gives me a 404, because TastyPie is trying to locate the resource identified by "None", which does not exist. Do you not get a 404 when you try this?Saccharometer
Updated with the code that worked for me. Looks like passing a javascript null did the trick for me. Maybe your error is elsewhere? Can you update this particular some_field to another object besides None?Lesseps
Yes, I am able to update some_field to another object, but null does not work for me. I had to add custom code to obj_update() to check for None to make it work.Saccharometer
So did the code I posted work for you? I definitely was able to update to None with that call.Lesseps

© 2022 - 2024 — McMap. All rights reserved.