3

I am running a Django site on Apache which is front'ed by Nginx instance to serve my static media.

I expose an API via django-tastypie to a model that I need to PATCH a field on. When I do local testing (via the django runserver) everything works as expected. On the live server however I get "400 (Bad Request)" returned.

I've read a few places saying that Nginx does not support PATCH? Is that right? Is there a good workaround for this? Am I doing something wrong?

I only send through the fields I want to update via the postData.

JQuery Code:

$.ajax({url: '...',
    type: 'PATCH',
    accepts: 'application/json',
    contentType: 'application/json',
    dataType: 'json',
    data: postData,
    processData: false,
    success: function() {
        // Success Code!
    },
    error: function() {
        // Error Code!
    }
});

Tastypie Resource:

class ReceivedMessageResource(ModelResource):
    """
    """
    campaign = fields.ForeignKey(CampaignResource, 'campaign')
    campaign_name = fields.CharField(readonly=True)
    campaign_id = fields.IntegerField(readonly=True)
    message_type = fields.CharField(readonly=True)
    display_date = fields.CharField(readonly=True)
    attachments = fields.ToManyField('apps.campaign.api.AttachmentResource',
                                     'attachment_set',
                                     related_name='message',
                                     full=True)

    class Meta:
        queryset = ReceivedMessage.objects.all()
        resource_name = 'message'
        filtering = {'id': ALL,
                     'campaign': ALL_WITH_RELATIONS}
        excludes = ['reason', 'provider', 'loyalty_profile', 'original_message', 'date_received']
        allowed_methods = ['get', 'post', 'put', 'delete', 'patch']
        paginator_class = ReceivedMessagesPaginator
        authentication = ApiKeyAuthentication()
        authorization = DjangoAuthorization()

Any direction on how to sort this will be appreciated :)

  • What's the URL you're trying to PATCH? api/v1/message/ or api/v1/message/1/ ? – santiagobasulto Aug 13 '12 at 10:42
  • I'm trying to patch /api/v1/message/1/ Still no joy. I saw this [post](http://stackoverflow.com/questions/10069871/heroku-and-django-with-405-error) that seems to suggest I need to do something to Nginx but for the life of me I cannot find what that is :-/ – Christopher Penkin Aug 13 '12 at 16:49
  • You should start trying to realize what "layer" the problem is. If it's on your HTTP server or in your application (Tastypie). Try removing authentication and authorization and doing some tests. Also, add this to your Meta class: detail_allowed_methods = ['patch'] – santiagobasulto Aug 13 '12 at 17:21
  • Ok, I'm going to give that a bash. I'm pretty convinced it's on HTTP server side as the code works fine in my dev environment on my local box using the django test server. It's only in production using the Nginx and Apache servers that the code fails and it only fails on this call with the PATCH method. The other GET and POST methods work as expected. – Christopher Penkin Aug 13 '12 at 20:00
  • I was checking the server logs and when doing a PATCH request the Nginx server does not even log the call. It kicks it out straight away. – Christopher Penkin Aug 14 '12 at 05:57
  • @penkin: The problem may be in the browser, not necessarily on the server. Browser's support for HTTP methods other that GET and POST is not something you should rely on. See my answer for a solution. – Tadeck Aug 16 '12 at 11:24

2 Answers2

4

If you are using the latest version of TastyPie (the one from GitHub repository, since August 5th), you can follow the instructions from the documentation:

Using PUT/DELETE/PATCH In Unsupported Places

Some places, like in certain browsers or hosts, don’t allow the PUT/DELETE/PATCH methods. In these environments, you can simulate those kinds of requests by providing an X-HTTP-Method-Override header. For example, to send a PATCH request over POST, you’d send a request like:

curl --dump-header - -H "Content-Type: application/json" -H "X-HTTP-Method-Override: PATCH" -X POST --data '{"title": "I Visited Grandma Today"}' http://localhost:8000/api/v1/entry/1/

So if your host does not support this method, add X-HTTP-Method-Override header with the name of the method you are trying to perform.

Community
  • 1
  • 1
Tadeck
  • 117,059
  • 25
  • 140
  • 191
  • Trying this out and just going through my logs and it seems that the X-HTTP-Method-Override header is not being passed from my Nginx to my Apache servers. Do you know what proxy_pass setup I'd need to add to my Nginx conf? – Christopher Penkin Aug 17 '12 at 05:32
  • I have confirmed that the Apache instance (behind Nginx) is not getting the X-HTTP-Method-Override header. I assume I need to somehow enable my Nginx to pass this header through to Apache. – Christopher Penkin Aug 17 '12 at 05:44
  • 1
    Found my issue. I was passing the headers in JQuery like `headers: {'X_HTTP_METHOD_OVERRIDE': 'PATCH'},` and it should have been `headers: {'X-HTTP-Method-Override': 'PATCH'},` – Christopher Penkin Aug 17 '12 at 05:49
0

If PATCH isn't getting past your HTTP server, you could fake it. Use a POST request, and add the header 'X-HTTP-Method-Override': 'PATCH'. This is supported in the master branch of Tastypie at the time of this posting.

If you are using an older version, such as the current stable release 0.9.11, you may need a small patch. Something like this gist will teach Tastypie to use that header.

The relevant piece is here:

    if request_method == 'post' and 'HTTP_X_HTTP_METHOD_OVERRIDE' in request.META:
        request_method = request.META['HTTP_X_HTTP_METHOD_OVERRIDE'].lower()
dokkaebi
  • 8,294
  • 3
  • 38
  • 58
  • 1
    The idea is interesting, but not necessary: TastyPie already supports `X-HTTP-Method-Override` header in a more flexible way. I have posted my answer with relevant information. – Tadeck Aug 16 '12 at 10:49
  • Indeed, it looks like they've added support for this in the latest version. Updating my answer. – dokkaebi Aug 17 '12 at 01:49