Django client.get response has no 'data' attribute - python

TL;DR I can't figure out why a response object has a content attribute but not a data attribute. I'm learning about Django REST Framework using this tutorial and referencing the testing approaches here.
I'm trying to unit test a REST API response using Django REST Framework. The test is simply trying to assert that the response data length matches the number of objects created in the test, like so:
from django.urls import reverse
from rest_framework.test import APITestCase
from project.models import SomeObject
class ObjectAPITest(APITestCase):
def test_get_list_returns_list_of_objects(self):
SomeObject.objects.create()
SomeObject.objects.create()
response = self.client.get(reverse('object-list'))
self.assertEqual(len(response.data), 2)
Right now, for the sake of testing, I have a model that only has a uuid:
import uuid
from django.db import models
class SomeObject(models.Model):
some_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
And then a simple serializer class:
from rest_framework import serializers
from project.models import SomeObject
class SomeSerializer(serializers.ModelSerializer):
class Meta:
model = SomeObject
fields = ['some_id']
And of course all of this is wired up in a very simple view:
from django.http import JsonResponse
from project.models import SomeObject
from project.serializers import SomeSerializer
def some_list(request):
some_objects = SomeObject.objects.all()
serializer = SomeSerializer(some_objects, many=True)
return JsonResponse(serializer.data, safe=False)
When I run the test, I get this error:
AttributeError: 'JsonResponse' object has no attribute 'data'
But I know that the response has something, because if I add print(response.content) I get output I'd expect:
b'[{"some_id": "241b4a0e-99d4-4239-8034-3afdd77ccb0d"}, {"some_id": "b21d787c-82ec-4e0e-9784-522d84079016"}]'
I expect response.data to return something, because of this specific tip from the Djanto REST Framework testing documentation:
it's easier to inspect response.data
Granted I'm sure I can fix the test by parsing response.content, but, since I'm learning, I'm really baffled by why I can't use response.data despite it being the recommended approach.

It turns out that the testing documentation assumes you're using the Response class from rest_framework.response rather than the JsonResponse class from django.http.response.
Updating the view to this solved the problem:
from rest_framework.response import Response
from project.models import SomeObject
from project.serializers import SomeSerializer
#api_view(['GET'])
def some_list(request):
some_objects = SomeObject.objects.all()
serializer = SomeSerializer(some_objects, many=True)
return Response(serializer.data)

Related

Why using validators=[URLValidator] when defining a model CharField doesn't make it check for URL-validity upon saving the corresponding ModelForm?

app.models.py:
from django.core.validators import URLValidator
from django.db import models
class Snapshot(models.Model):
url = models.CharField(max_length=1999, validators=[URLValidator])
app.forms.py:
from django import forms
from .models import Snapshot
class SnapshotForm(forms.ModelForm):
class Meta:
model = Snapshot
fields = ('url',)
app.views.py:
from django.http import HttpResponse
from .forms import SnapshotForm
def index(request):
snapshot_form = SnapshotForm(data={'url': 'not an URL!'})
if snapshot_form.is_valid():
snapshot_form.save()
return HttpResponse("")
Why does it save 'not an URL!' into DB, despite the fact that it ain't a valid URL?!
What is the right way to incorporate URLValidator?
You've specified the validators for your field like so: validators=[URLValidator]. This is incorrect because the validators argument takes in a list of callables that perform the validation. In this case even though URLValidator is callable but that's actually the __init__ method of the class being called. You need to pass an instance of the class for it to work properly:
# Note the brackets after URLValidator to instantiate the class
url = models.CharField(max_length=1999, validators=[URLValidator()])
Better yet since you want to take input a URL you should simply use the URLField class for your field:
url = models.URLField(max_length=1999)

Serialize custom data in DRF

I have two types of static data and don't need any models because I don't want to save those data in the database. The data are generated from a class RandomObjectGenerator.
Data 1 is a string whose length could be up to 2,097,152
Date 2 is a JSON data whose size could be up to 4 like {'a':1, 'b':2, 'c':3, 'd':4}
views.py
from django.shortcuts import render
from rest_framework import views
from rest_framework.response import Response
from .generate import RandomObjectGenerator
from .serializers import ObjectSerializers
import json
# Create your views here.
class ApiView(views.APIView):
def get(self, request):
randomObject=RandomObjectGenerator().get_random_objects()
randomObjectCount=RandomObjectGenerator().get_random_object_count()
objectSerialize=ObjectSerializers(data={'randData':randomObject,'randCount': json.dumps(randomObjectCount)})
if objectSerialize.is_valid():
objectSerialize.save()
return Response(objectSerialize.data)
else:
return Response(objectSerialize.errors)
serializers.py
from rest_framework import serializers
class ObjectSerializers(serializers.Serializer):
randData=serializers.CharField()
randCount=serializers.IntegerField()
I am getting two types of errors here. If I use serializers.IntegerField() then I am getting the "A valid integer is required." error then if I try to use serializers.CharField() then it's showing create() must be implemented.
I got stuck for while and couldn't get any idea how i fixed it.
create() need to be implemented on any serializer you call .save() because calling create is what save does.
So the question really is, why are you calling .save() if you have no intention of saving this to a database? what are you conceptually saving here?
Removing the row with save() from the view should solve your issue.

Why I get an error when adding __init__(self) method to Django rest framework viewset class?

I Am keep getting a error when trying to build a Django API.
I have this class:
from uuid import UUID
from django.shortcuts import render
from django.http.response import JsonResponse
from django.http.request import HttpRequest
from rest_framework import viewsets, status
from rest_framework.parsers import JSONParser
from rest_framework.response import Response
from Instruments import serializers
from Instruments.services import InstrumentsService
from rest_framework.decorators import api_view
from Instruments.services import InstrumentsService
from Instruments.models import Instrument
from Instruments.serializers import InstrumentsSerializer
# Application views live here
class InstrumentViewSet(viewsets.ViewSet):
# instruments = Instrument.objects.all()
def __init__(self):
# self.instrument_service = InstrumentsService()
# self.instruments = Instrument.objects.all()
super().__init__()
def list(self, request: HttpRequest):
try:
self.instruments = Instrument.objects.all()
serializer = InstrumentsSerializer(self.instruments, many=True)
# data = self.instrument_service.get_instruments()
data = serializer.data
return JsonResponse(data, status=status.HTTP_200_OK, safe=False)
except Exception as exc:
return JsonResponse(
{"Status": f"Error: {exc}"},
status=status.HTTP_400_BAD_REQUEST,
safe=False,
)
when the init() method is defining even if it is just doing pass the django server gives me this error when I send a request:
TypeError at /api/
__init__() got an unexpected keyword argument 'suffix'
If I remove or comment out the init() method it works.why??
The “got an unexpected keyword argument” exception is rather descriptive. What it means is your class instance (in this case, your ViewSet instance) was initialized with a keyword argument that you’re not handling. That can be fixed by doing the following in your init method:
def __init__(self, **kwargs):
# self.instrument_service = InstrumentsService()
# self.instruments = Instrument.objects.all()
super().__init__(**kwargs)
This is required because the super class (View) utilizes **kwargs when the instance is initialized.
For the record, this is not using Django as it was intended. Django was never meant for service layers and using the init method like this is counterproductive since a ViewSet takes a queryset variable. I would encourage you to read the documentation much more thoroughly before continuing with this project of yours.

Define renderer class dynamically in Django view function

How can I define the renderer class inside the Djanog old api_view function depending on some condition? To have something like this:
#api_view(['GET'])
def can_render_2_things(request):
if some_comdition:
renderer_classes = [PDFRenderer]
else:
renderer_classes = [JSONRenderer]
From Django docs you can use different responses objects:
from django.http import FileResponse, JsonResponse
#api_view(['GET'])
def can_render_2_things(request):
if some_comdition:
response = FileResponse(open('myfile.png', 'rb'))
else:
response = JsonResponse({'foo': 'bar'}, encoder=myJsonEncoder)
You can set the attributes accepted_renderer and accepted_media_type on the request yourself if the renderer_classes list you want to set contains only one renderer class. This will cause the correct renderer to be used:
#api_view(['GET'])
def can_render_2_things(request):
if some_comdition:
request.accepted_renderer = PDFRenderer
request.accepted_media_type = PDFRenderer.media_type
else:
request.accepted_renderer = JSONRenderer
request.accepted_media_type = JSONRenderer.media_type
But this is not very useful if you want to set more renders, I would advice you to simply move to using class based views instead of function based ones and use the APIView:
from rest_framework.views import APIView
class CanRender2Things(APIView):
def get(self, request, format=None):
if some_comdition:
self.renderer_classes = [PDFRenderer]
else:
self.renderer_classes = [JSONRenderer]

Renaming DRF serializer fields

I'm using DRF serializers to validate incoming data that I retrieve from a JSON API. I'm trying to rename some awkwardly named fields from the response, making it easier to use the serializer.data further on in my code.
Data received from the API looks like this:
{"FunnyNamedField": true, "AnotherWeirdField": false}
And handling code:
resp = requests.get([...])
resp.raise_for_status()
ser = MyFunnyDataSerializer(data=resp.json())
if ser.is_valid():
do_domething_with(ser.data)
I would like the serializer to translate the incoming field names to something more consise. ser.data could look like: {'funny': True, 'weird': False}.
What I tried but doesn't work as I hoped:
class MyFunnyDataSerializer(serializers.Serializer):
funny = serializers.Booleanfield(source='FunnyNamedField')
Is there any way to achieve this without reverting to a SerializerMethodField?
You can override BaseSerializer to achieve this:
from rest_framework import serializers
class CustomSerializer(serializers.BaseSerializer):
def to_representation(self, instance):
return {
<datas>
}
You can do some specific modifications on instance serialization with custom methods.
Another solution could be to write your own validator for one field: Field Validator Method.
So in this documentation example you could modify value before return it.
from rest_framework import serializers
class BlogPostSerializer(serializers.Serializer):
title = serializers.CharField(max_length=100)
content = serializers.CharField()
def validate_title(self, value):
"""
Check that the blog post is about Django.
"""
if 'django' not in value.lower():
raise serializers.ValidationError("Blog post is not about Django")
if value == "something":
value = "something_else"
return value
Hope it helps.

Categories