Django test client does not automatically serialize factories - python

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.

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

Related

Object has no attribute get in serializer

I created a serializer and an API endpoint so I can retrieve some data from a Django DB in my React app but getting this error message:
AttributeError: 'ProgrammingChallengesView' object has no attribute 'get'
Here is my models.py:
#creating programming challenges
class ProgrammingChallenges(models.Model):
challenge_id = models.AutoField(primary_key=True)
challenge_name = models.CharField(max_length=200)
challenge_description = models.TextField()
challenge_expectations = models.TextField()
my serializer:
from accounts.models import ProgrammingChallenges
...
class ProgrammingChallengesView(serializers.ModelSerializer):
class Meta:
model = ProgrammingChallenges
fields = '__all__'
and my urls.py:
path('api/programming_challenges/', ProgrammingChallengesView, name='programming_challenges'),
Thanks to the comments; I clearly didn't understand that a serializer only transforms my data to make it available through an API. I still had to create a view for my API's endpoint.
I opted to create a ReadOnlyModelView because I only want to GET data from this endpoint.
Here is what I wrote in my views:
class ProgrammingChallengesView(ReadOnlyModelViewSet):
serializer_class = ProgrammingChallengesSerializer
queryset = ProgrammingChallenges.objects.all()
#action(detail=False)
def get_list(self, request):
pass
and in my urls.py:
path('api/programming_challenges/', ProgrammingChallengesView.as_view({'get':'list'}), name='programming_challenges'),
I think you shouldn't hurry read the docs again. You are trying to use serializers as views.
Models - are representation of db tables as class.
Serializer serializes the data to json.
View accepts the reqeust from client and returns a Response.
Code shoudld be:
models.py
class ProgrammingChallenge(models.Model):
name = models.CharField(max_length=200)
description = models.TextField()
expectations = models.TextField()
Your model name should be ProgrammingChallenge(singular) not ProgrammingChallenges(plural).
You should't add prefix challenge before all field names. Because we already know that the fields are in a Model called ProgrammingChallenge. And it is easy to access them like ProgrammingChallenge.name than ProgrammingChallenge.challenge_name
You don't have to add field id manually. Django model automatically adds id field as primary_key
serializer.py
from accounts.models import ProgrammingChallenge
...
class ProgrammingChallengeSerializer(serializers.ModelSerializer):
class Meta:
model = ProgrammingChallenge
fields = '__all__'
No problem in serialize.
Now, main problem is you don't have any view. You definetly read docs. You can use APIView, generic views or viewset. In this example i'm going to use ViewSet that handles CRUD operations built in.
viewsets.py
from rest_framework.viewsets import ModelViewSet
from .models import ProgrammingChallenge
from .serializers import ProgrammingChallengSerializer
class ProgrammingChallengViewSet(ModelViewSet):
queryset = ProgrammingChallenge.objects.all()
serializer_class = ProgrammingChallengeSerializer
urls.py
from rest_framework.routers import SimpleRouter
from .viewsets import ProgrammingChallenge
router = SimpleRouter()
router.register('challengr', ProgrammingChallengeViewSet)
urlpatterns = router.urls
Another advantage of using viewset, it also generate all endpoint for it's CRUD methods automatically via routes.
It should help you to start your first project.
AGAIN, READ THE DOCS!

Django, JSON serialize queryset and list of models has different behavior

I have a Django model with a function (on the Python class) that modifies some of its fields.
class TimeStampedModel(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class MyModel(TimeStampedModel):
user = models.ForeignKey(User, on_delete=models.CASCADE)
a_number = models.FloatField()
some_text = models.CharField(max_length=64)
def MyFunction(self):
self.a_number = 123.123
I can do this, and it works
from django.core.serializers.json import DjangoJSONEncoder
json.dumps(list(MyModel.objects.filter(...).values()), cls=DjangoJSONEncoder)
Now I'd like to call the function MyModel.MyFunction on all the models before JSON encoding them. I've tried this, but obviously it returns the fields as they are in the database, not modified by MyFunction:
from django.core.serializers.json import DjangoJSONEncoder
mymodels = MyModel.objects.filter(...)
for mymodel in mymodels:
mymodel.MyFunction()
# or mymodel.myfield = somethingelse
json.dumps(list(mymodels.values()), cls=DjangoJSONEncoder)
So I've tried this
from django.core.serializers.json import DjangoJSONEncoder
mymodels = MyModel.objects.filter(...)
_mymodels = []
for mymodel in mymodels:
mymodel.MyFunction()
# or mymodel.myfield = somethingelse
_mymodels.append(mymodel)
json.dumps(_mymodels, cls=DjangoJSONEncoder)
but TypeError: Object of type MyModel is not JSON serializable.
I've read on other answers that you'd have to either install django rest framework or implement a to_json function on MyModel.
However, I'd like not to clutter my application to achieve a behavior that is already available. How can I tell django to behave the same way on an array of models that it behaves on a queryset of the same model?
django.forms.models.model_to_dict did the trick
from django.core.serializers.json import DjangoJSONEncoder
from django.forms.models import model_to_dict
mymodels = MyModel.objects.filter(...)
_mymodels = []
for mymodel in mymodels:
mymodel.MyFunction()
# or mymodel.myfield = somethingelse
_mymodels.append(model_to_dict(mymodel))
json.dumps(_mymodels, cls=DjangoJSONEncoder)
Another alternative is to try using json.dumps(MyModel.objects.filter(...).values(), cls=DjangoJSONEncoder), as this will mostly do the same thing as model_to_dict, but without the overhead of creating model instances (from something like a dict) and then converting them back to dicts.
You may need to write a custom DjangoJSONEncoder subclass if you use any custom field types, to instruct json.dumps() how to encode those.

how to form create methods to save the data to the database using django rest framework

I am trying to save the data from the JSON file to the Database using the Deserialization the serializers in Django using the drf and these are my models:
from django.db import models
class elements(models.Model):
id=models.CharField(primary_key=True,max_length=100)
slug=models.CharField(max_length=100)
name=models.CharField(max_length=100)
courseType=models.CharField(max_length=100)
def __str__(self):
return "%s %s %s %s" % (self.id, self.slug,self.name, self.courseType)
class types(models.Model):
elements=models.ForeignKey(elements, on_delete=models.CASCADE)
and the serializer files are as follows:
from rest_framework import serializers
from serial.models import elements,types
class elementserializer(serializers.ModelSerializer):
class Meta:
model = elements
field=('id','name','slug','courseType')
class courseserial(serializers.ModelSerializer):
elements=elementserializer(many=True)
class Meta:
model = types
field=('elements')
the create methods that I use keeps giving errors even when I tried using the documentation http://www.django-rest-framework.org/api-guide/relations/#writable-nested-serializers can anyone tell me a better way to write the create methods to save the data to the database using the serializers. The create method that I use is as follows:
def create(self, validated_data):
elements_data = validated_data.pop('elements')
types = types.objects.create(**validated_data)
for elements_data in elements_data:
elements.objects.create(types=types, **elements_data)
return types

Serializing NotificationQuerySet from django-notifications-hq not working

So, i'm trying to add to my API made with DRF (Django REST Framework) the notifications Model, but i'm getting this error:
AttributeError: 'NotificationQuerySet' object has no attribute 'recipient'
I'm trying to serialize a django app model, Notification. It's from this app:
https://github.com/django-notifications/django-notifications
My ViewSet class is this:
class NotificationsViewSet(viewsets.ViewSet):
serializer_class = NotificationsSerializer
def list(self, request):
queryset = Notification.objects.all()
return Response(NotificationsSerializer(queryset).data)
And here my serializer:
class NotificationsSerializer(serializers.ModelSerializer):
class Meta:
model = Notification
fields = ('recipient','description')
depth = 0
So, when data pass to serializer, it becomes "Void" or without any data.
Doing something like into the list method:
print queryset[0] returns a Notification object normaly. But when passing this queryset to the serializer, seems to be null, and the AttributeError comes.
Also, tried this with the console:
notifications = Notification.objects.all()
That returns a NotificationQuerySet object (iterable). Then I can:
for noti in notifications:
print noti
That would output all the unicode methods of every notification.
With every Notification instance, i can also access to Model propierties:
for noti in notifications:
print noti.recipient
And works very well.
Why is not working when passing this to the serializer? Its weird...
You need to pass many=True when initializing a serializer with a queryset. DRF will assume you are passing a single object and try to get the fields directly from it if you do not tell it that you are passing in multiple objects.
Heres a full implementation where the readme leaves off for drf
urls.py
...
import notifications.urls
urlpatterns = [
...
path("inbox/notifications/", views.NotificationViewSet.as_view({'get': 'list'}), name='notifications'),
]
serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
class NotificationSerializer(serializers.Serializer):
recipient = UserSerializer(read_only=True)
unread = serializers.BooleanField(read_only=True)
target = GenericNotificationRelatedField(read_only=True)
verb = serializers.CharField()
views.py
from notifications.models import Notification
from .serializers import NotificationSerializer
NotificationViewSet(viewsets.ViewSet):
serializer_class = NotificationSerializer
def list(self, request):
queryset = Notification.objects.all()
return Response(NotificationSerializer(queryset, many=True).data)

Subclass properties don't appear when extending EndpointsModel

I would like to create a BaseModel that is an EndpointsModel to contain properties common across all my models (ie. created_at, modified_at). Then I would like to create a User model that extends that BaseModel.
However, I'm running into an issue where when I look at my "user.create" method in the API Explorer, the request body only shows only shows the BaseModel properties of created_at and modified_at, but not the username property.
Here's what I have:
from endpoints_proto_datastore.ndb import EndpointsModel
from google.appengine.ext import ndb
class BaseModel(EndpointsModel):
created_at = ndb.DateTimeProperty(auto_now_add=True)
modified_at = ndb.DateTimeProperty(auto_now=True)
class User(BaseModel):
username = ndb.StringProperty(required=True)
Here's the API built using Google Cloud Endpoints:
import endpoints
from google.appengine.ext import ndb
from models import User
from protorpc import remote
#endpoints.api(name='user', version='v1',
description='API for User Management')
class UserApi(remote.Service):
#User.method(name='user.create', path='user'):
def create_user(self, user):
user.put()
return user
application = endpoints.api_server([UserApi])
If you go to http://localhost:8080/_ah/api/discovery/v1/apis/user/v1/rest you'll see the discovery document generated by your API. Note that (toward the bottom) the create method on the user resource is shown as taking a BaseModel rather than a User.
Now I don't know why this happens precisely—it's definitely related to the magic being done by EndpointsModel—but I have been able to achieve the result you want by switching the inheritance around, and treating BaseModel like a mixin rather than a base class, this way the User model can inherit directly from EndpointsModel:
class BaseModel:
created_at = ndb.DateTimeProperty(auto_now_add=True)
modified_at = ndb.DateTimeProperty(auto_now=True)
class User(BaseModel, EndpointsModel):
username = ndb.StringProperty(required=True)
It makes sense then to rename BaseModel to something that makes more explicit it's a mixin now.
If you check the same discovery document (or API Explorer) you'll notice create takes a User message after this change.

Categories