POST with None data in Request Factory in Django
Asked Answered
S

3

11

I'm moving my django application from 1.x to 2.2, When running unit tests, I get a error about posting None as data. Is it allowed to post None in previous versions? Is there any way to post None via RequestFactory?

I don't want to give a empty string, since the field needs to be validated

r = RequestFactory()
rr = r.post("some-url",{"name":"sri", "kd_ratio":None})

Error:

  File "/usr/local/lib/python3.7/site-packages/django/test/client.py", line 354, in post
    post_data = self._encode_data(data, content_type)
  File "/usr/local/lib/python3.7/site-packages/django/test/client.py", line 313, in _encode_data
    return encode_multipart(BOUNDARY, data)
  File "/usr/local/lib/python3.7/site-packages/django/test/client.py", line 197, in encode_multipart
    'Cannot encode None as POST data. Did you mean to pass an '
TypeError: Cannot encode None as POST data. Did you mean to pass an empty string or omit the value?
Subglacial answered 11/9, 2019 at 4:24 Comment(3)
What is the error that you get? Please attach the stacktrace with the question.Kalasky
StackTrace has been attachedSubglacial
Use the format='json' argument: r.post('some-url', {'name': 'sri', 'kd_ratio': None}, format='json')Incredible
V
22

https://docs.djangoproject.com/en/4.2/topics/testing/tools/#django.test.Client.post

You need to add content_type="application/json" as an argument to be able to send None/null as a value.

The reason is that the default content type (multipart/form-data) doesn't support null values, only empty strings, hence the suggestion.

Volumeter answered 11/2, 2020 at 2:3 Comment(2)
Underrated answer that saves lives hahaCryptic
Truth, Django 4.1 docs are here: docs.djangoproject.com/en/4.1/topics/testing/toolsLalo
C
2

Hopefully this saves some time for someone else

This was my problem in Django 4.1 and Django Rest Framework 3.13.1.
res = self.client.patch("some-url", {"assigned_user": None})

@drew's suggestion did not solve it for me:
res = self.client.patch("some-url", {"assigned_user": None}, content_type="application/json")

Then I read the docs and understood that this works:
res = self.client.patch("some-url", {"assigned_user": ""})

But I didn't like it and luckily I found @Masood Khaari quickly-overread suggestion below the question:
res = self.client.patch("some-url", {"assigned_user": None}), format='json')

Cabanatuan answered 10/2, 2023 at 11:34 Comment(0)
S
0

I'll add my little contribution/observation here.

My view post() method is looking for request.POST.get("action"). Thus I couldn't set the content-type as per the accepted answer, since then that means that all my data is moved to the request.body. I'm not going to re-write all those just so I can test them.

Thus instead, I have set all None value that may be present in the test data sent to the views by an empty string (which is what the browser would actually send a "None" as anyways). In my case, the None value appeared to be fields in the Form that were not present for whatever reason the request.

def build_mock_post_data(form, formsets=[]):
    """ builds the k-v pairs that mimics the POST data sent by client, including managment forms & formsets """
    full_post_data = {}
    for formset in formsets:
        prefix = formset.prefix
        fdata = {}
        for i, f in enumerate(formset.initial_forms):
            for key, val in f.initial.items():                   # create the form field's keys
                fdata[f"{prefix}-{i}-{key}"] = str(val)
            fdata[f"{prefix}-{i}-id"] = str(f.fields["id"].initial )                
        fdata[f"{prefix}-TOTAL_FORMS"] = len(formset.initial_forms)
        fdata[f"{prefix}-INITIAL_FORMS"] = len(formset.initial_forms)               # since for test, we always consider the imported fixtures is whatebver will be tested. caller could update that valeur to simulate "new" dets added
        full_post_data.update(**fdata)

    # add main form data
    full_post_data.update(**form.initial)

    # check for None & replace them by empty strings, otherwise issues with django.Client.post(...)
    nones = [k for k,v in full_post_data.items() if v is None]
    for n in nones:
        full_post_data[n] = ""
    return full_post_data

Then in the tests where I have post data I need to sent:

# prepare post data ...
post_data = build_mock_post_data(form, formsets=[formset])
post_data["action"] = "soumpick"

# TODO: make call
response = self.client.post(reverse('soumission:update_open', args=(ent_soum.id, )), data={**post_data})
r = json.loads(response.content.decode())
log.info(f"Response: {r}")
Sisson answered 12/11, 2021 at 2:55 Comment(1)
Would you have a piece of code to share? Have tried both the accepted answer and yours but still can't seem to get things working.Zacheryzack

© 2022 - 2024 — McMap. All rights reserved.