Shortcut for test client json requests

The way to issue a JSON request in test client is to specify content_type as application/json.

client.post(url, data, content_type="application/json")

I would like to propose a shortcut for this because it’s a very common case. Something like

client.post(url, data, json=True)

Would really appreciate your thoughts on this proposal before creating a ticket.

I don’t think this would be a good idea.

The content_type refers to the http header being set by this parameter. Providing a shortcut for one single value - when that value is easily specified by the proper parameter - seems like too much a niche benefit.

If it’s a real issue, you could always subclass the test.Client class to make that setting the default for your test cases, then you don’t need to supply that parameter at all.

I agree with Ken, subclassing is the easy tool for shortening this parameter in your code.

You could also use ** on a dict to achieve similar with fewer pieces:

json_request = {"content_type": "application/json"}

...

class MyTests(...):
    def test_something(self):
        response = self.client.post(url, data, **json_request)

Or functools.partial:

from functools import partial

class MyTests(...):
    def setUp(self):
        self.json_post = partial(self.client.post, content_type="application/json")
    
    def test_something(self):
        response = self.json_post(url, data)

(See my blog for more ideas using partial: Three more uses for functools.partial() in Django - Adam Johnson , Three uses for functools.partial() in Django - Adam Johnson . )

Python gives you many tools for shortening code :slight_smile:

2 Likes

Makes sense, Thanks! Would it make sense to include such a subclass as a builtin?

This proposal may be worth a revisit once this has landed: Request for Steering Council vote on modernising the request object.

Although I imagine it might not be the API described in this proposal but something a bit more flexible, which of course might not be any change at all.

I (too) would be -1 for this change: it expands the API surface area for no real gain.

The suggested workarounds seem more than sufficient if this is desired. I don’t think that should be part of core, but rather something folks add in their own projects.

1 Like

Would there be interest in passing the data directly to a json kwarg? This will put Django in line with django-ninja and FastAPI.

response = client.post(json={"foo": "bar"})

In the meantime, here’s my current workaround for anyone interested:

from django.test import Client as _Client

def take_json(method):
    """Wrap `Client` methods to accept a `json` kwarg."""

    def inner(self, *args, json=None, **kwargs):
        if json is not None:
            kwargs["content_type"] = "application/json"
            kwargs["data"] = json
        return method(self, *args, **kwargs)

    return inner

class Client(_Client):
    patch = take_json(_Client.patch)
    post = take_json(_Client.post)
    put = take_json(_Client.put)

DRF’s client will also handle JSON directly:

from rest_framework.test import APIClient

client = APIClient()
client.post('/notes/', {'title': 'new idea'}, format='json')

I suppose adding this to Django’s client has always been pending adding JSON handling to the request object (which was what @nanorepublica pointed to as well). I’m hoping we can advance that in the next cycle.