简体   繁体   中英

Django test client does not automatically serialize factories

Here's my code:

# models.py
class MyModel(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    name = models.CharField(max_length=10)
    ...

# views.py
def get_all_models(request):
    return JsonResponse({"models": list(MyModel.objects.all())})

# urls.py
path('/mypath', views.get_all_models, name='get_all_models'),

This code works just fine if I visit /mypath . However, when I run an automated test using Django's test client, I get this error:

*** TypeError: Object of type MyModel is not JSON serializable

this is my test: from django.test import TestCase, Client from blog.tests.factories.user import UserFactory from blog.tests.factories.post import PostFactory

class MyModelTest(TestCase):
    def setUp(self):
        self.user = UserFactory.create()
        self.post = MyModelFactory.create(user=self.user)
        self.client = Client()

    def test_get_all_models(self):
        response = self.client.get("/mypath")
        pass

I suspect it has something to do with my factories:

import factory
from .models import User, MyModel

class UserFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = User

    username = factory.Faker('word')
    email = factory.Faker('email')

class MyModelFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = MyModel

    user = factory.SubFactory(UserFactory)
    name = factory.Faker('name')

How can I make my factories serializable?

NOTE: This question is not a duplicate. The other questions linked have view handlers that return HttpResponse objects, instead of JsonResponse objects. This distinction is key to my problem, because the error I'm seeing is related to JSON serialization that is supposed to be addressed by the JsonResponse class.

Also, the other questions do not involve factories. Factories are another key component of what I'm trying to do, which is run integration tests against data generated by factories.

This code works just fine if I visit /mypath.

I copy and paste your code and it raised a TypeError as exactly same as this post, Django object is not JSON serializable


I don't think this is something wrong with your test client. Change your views.py as below and try again.




def get_all_models(request):
    
    return JsonResponse({"models": })

This snippet will produce a JSON response as below,

{
    "models": [
        {
            "model": "account.mymodel",
            "pk": 1,
            "fields": {
                "user": 74,
                "name": "jerin"
            }
        },
        {
            "model": "account.mymodel",
            "pk": 2,
            "fields": {
                "user": 66,
                "name": "peter"
            }
        }
    ]
}

Why json.loads() ??

The serialized out put will be in string , we've to change it else, the Jsonresponse will be a string instead of Json

In [1]: from account.models import MyModel                                                                                                                                                                         

In [2]: from django.core import serializers                                                                                                                                                                        

In [3]: data = serializers.serialize('json', MyModel.objects.all(), fields=('user', 'name'))                                                                                                                       

In [4]: data                                                                                                                                                                                                       
Out[4]: '[{"model": "account.mymodel", "pk": 1, "fields": {"user": 74, "name": "jerin"}}, {"model": "account.mymodel", "pk": 2, "fields": {"user": 66, "name": "peter"}}]'

In [5]: type(data)                                                                                                                                                                                                 
Out[5]: str

In [6]: import json                                                                                                                                                                                                

In [7]: data_new = json.loads(data)                                                                                                                                                                                

In [8]: data_new                                                                                                                                                                                                   
Out[8]: 
[{'model': 'account.mymodel',
  'pk': 1,
  'fields': {'user': 74, 'name': 'jerin'}},
 {'model': 'account.mymodel',
  'pk': 2,
  'fields': {'user': 66, 'name': 'peter'}}]

In [9]: type(data_new)                                                                                                                                                                                             
Out[9]: list

Read more about Serialization of Django Objects/QuerySets



UPDATE-1

Why doesn't this work out of the box? It seems cumbersome to use.

I don't see any non-out of the box method here. Because all things seem good, in Pythonic way and in Django way (my answer).

From here, we could understand what is a pure JSON.

An object is an unordered set of name/value pairs. An object begins with { (left brace) and ends with } (right brace). Each name is followed by : (colon) and the name/value pairs are separated by , (comma).

In your OP, you are trying to return {"models": list(MyModel.objects.all())} which is a dict but not a JSON .
Yeah...the outer layers are dict so it's can be a JSON Array . But the Array contents are QuerySets , which is not a value according to this content


UPDATE-2

I found a workaround to pass objects to the serializer class. Create a function as below,

from django.core import serializers


def serialize_dup(format, queryset, **options):
    

    return serializers.serialize(format, queryset, **options)

and use this serialize_dup() funtion as usual serializers.serialize()


Update-3

As @fush suggested in comments, it's possible to return the JSON response if you serialize the model objects with format='python'

# code sample
from django.http.response import JsonResponse
from django.core import serializers


def get_all_models(request):
    data = serializers.serialize( MyModel.objects.all(), fields=('user', 'name'))
    return JsonResponse({"models": data})

The code you shared assumes JSONResponse will serialize an ORM object, but according to Django documentation, it won't:

https://docs.djangoproject.com/en/3.0/ref/request-response/#jsonresponse-objects

https://docs.djangoproject.com/en/3.0/topics/serialization/#djangojsonencoder

It will work if you serialize the Django ORM object before passing it to JSONResponse

Consider doing the following:

from django.core import serializers
data = serializers.serialize("json", MyModel.objects.all())

https://docs.djangoproject.com/en/3.0/topics/serialization/

django-rest-framework is a very popular lib used in scenarios like the one you shared https://docs.djangoproject.com/en/3.0/topics/serialization/#djangojsonencoder

what about this:

def get_all_models(request):
    return JsonResponse({"models": list(MyModel.objects.all().values())},safe=False)

the point is here:

MyModel.objects.all().values()
safe=False

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM