Custom endpoint with basic serializer? - python

In my model's viewset, I have created a custom endpoint and I'm trying to serialize some quick meta information to send out.
Endpoint Declaration:
#list_route(methods=['get'], url_path='meta')
def get_meta_details(self, request):
serializer = ThingMetaSerializer
return Response(serializer.data)
ThingMetaSerializer:
class ThingMetaSerializer(serializers.Serializer):
some_data = serializers.SerializerMethodField(method_name='ret_zero')
def ret_zero(self):
return 0
Every time I run the endpoint I get the following error:
TypeError(repr(o) + " is not JSON serializable")
Any idea how I can make this work?
Edit:
I made this work using the following code in the viewset:
meta_data = {
'some_data': 0,
}
#list_route(methods=['get'], url_path='meta')
def get_meta_details(self, request):
# do some calculations
return JsonResponse(self.meta_data)
However this method does not auto generate into the Django RF Schema. If there is a better way of doing this I would love to know.

serializer = ThingMetaSerializer
This doesn't initialize the serializer. You should use:
serializer = ThingMetaSerializer(instance={'some field': 'some data'})
instead.

Related

How to Access method decorated with #action decorator of a view inherited from APIView Class in urls.py file in DRF?

I need to apply different permission on different methods of my View class which is inherited from APIView in a DRF application. To achieve this I am using #action decorator on my methods of view.
Here is views.py code
class UserView(APIView):
#action(methods=['post'], detail=False, permission_classes=[AllowAny])
def create(self, request):
serializer = UserRegSerializer(data=request.data)
user_service = UserService()
try:
serializer.is_valid(raise_exception=True)
serialized_data = serializer.validated_data
registered_user = user_service.create_user(serialized_data)
payload = registered_user.__dict__
response = ResponseGenerator(payload, constants.SUCCESS_KEY)
except Exception as e:
response = ResponseGenerator(e)
return Response(data={"Response": response.get_custom_response()})
I am not getting that how could I access this method in my urls.py file against the pattern '/user', here is my urls.py code.
urlpatterns = [
path('user', UserView.as_view()),
]
I was using ChatGPT to answer this query of mine. It suggested some ways to use a dictionary object passed {'post':'create'} in as_view() method of View in urls.py as following.
urlpatterns = [
path('user', UserView.as_view({'post':'create'})),
]
In this it told me that key of the dictionary should be the http method used to access that method against the url pattern and value of dictionary should be the method which you want to access against the given url.
But its not working, and gives me following error when I try to start my project after writing this code in my urls.py
TypeError: APIView.as_view() takes 1 positional argument but 2 were given
I didn't find any solution to solve the problem that I am facing anywhere on Internet and in django's documentation as well.
I recommend you using Generic(Some Method)APIView.
Because it can be changed to something good for your code scalability and reliability.
Use CreateAPIView, you don't need to decorator for basic create function.
# views.py
class UserRegView(CreateAPIView):
serializer_class = UserRegSerializer
permission_classes = [AllowAny]
If you need validation, write at the validate function.
Your Response could be change sometime. Then, you should change you
# serializers.py
class UserRegSerializer(serializers.Serializer):
somefield_1 = serializers.CharField()
somefield_2 = serializers.CharField()
newfield_1 = serializers.IntegerField(read_only=True) # example
def validate(self, attrs):
a = attrs["somefield_1"]
if a != "otherthing":
raise ValidationError({"error": "Your error description some case"})
return attrs
def create(self, validated_data):
instance = UserReg.objects.create(**validated_data)
# Make Logic for instance or just validated_data or custom response whatever you want.
# If you want to custom response, could write like this simple example.
return {
"somefield_1": "test1",
"somefield_2": "test1",
"newfield_1": "test1",
}
Now, you don't need any argument in as_view().
# urls.py
urlpatterns = [
path("user_new", UserRegView.as_view()),
]

Celery EncodeError(TypeError('Object of type Response is not JSON serializable'))

I am using Celery - Redis - Django rest framework together.
The error happens when I try to pass the serializer to the delay of celery within the Django rest framework.
Here is the viewset
class TestSet(viewsets.ModelViewSet):
queryset = Test.objects.all()
serializer_class = ImageSerializer
def create(self, request, *args, **kwargs):
serializer = TestSerializer(data=request.data)
if serializer.is_valid():
image_uploaded= "static_path"
json_data = base64.b64encode(np.array(image_uploaded)).decode('ascii')
result = test_call.delay({'image': json_data})
result = test_call.delay(serializer)
data = {"task": result.task_id}
return Response(data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
#shared_task(name="values")
def test_call(decoded_image):
return decoded_image
The error I get is
EncodeError(TypeError('Object of type Response is not JSON
serializable'))
Update:
Even when I do this, I still get an error
result = test_call.delay({"task": 1})
#shared_task(name="values")
def test_call(decoded_image):
return {"task": 2}
This isn't going to answer your question, but I can't leave a comment (low reputation).
It seems that you are trying to JSON Serialize something that obviously isn't JSON serializable. Based on the name, it is some kind of image data. You could try a different workflow that should be JSON Serializable, example:
One Example:
first save the image somewhere that is accessible later and add the location in the serializer (S3 bucket and then a link to the image)
In your celery task, fetch the image data based on that location
Second Example:
convert the image data into something JSON serializable like a Base64 image string

How can I GET external data and combine it with my current view?

I have a view that I want to use to pull Model data + get data from an external API. Using the same URL path to get database data + external API data, as they are both relatively connected in terms of business logic.
This is how I tried implementing it:
class BucketList(generics.ListAPIView):
permission_classes = [IsAuthenticated]
serializer_class = BucketListSerializer
filter_backends = [OwnerOrUserFilterBackend]
queryset = Bucket.objects.all()
# I use a field in my model as part of my GET request
def get(self, request, *args, **kwargs):
bucket_symbols = Bucket.objects.only('stock_list')
symbols_unicode = (','.join(map(str, bucket_symbols)))
postgrest_urls = f"http://localhost:3000/rpc/bucketreturn?p_stocks=%7B{symbols_unicode}%7D"
response = requests.get(postgrest_urls, headers={'Content-Type': 'application/json'}).json()
return Response(response, status=status.HTTP_200_OK)
The idea of def get(), is to extract every stock_list field in objects, and feed that into my other API on localhost. To clarify, I want every stock_list in object passed into my get requests and returned, for each object.
However I keep getting undefined errors in my JSON response.
How can I properly implement a two-in-one view solution for my view, I still want to keep my original queryset = Bucket.objects.all() in my view.

Use existing ModelSerializer with JSONResponse

I have a Twitter authentication view that doesn't use a viewset so the auth can be handled on the backend. The view takes in the oauth_token & uses Twython to get the profile & create a Twitter model.
Currently I just return status 201 on success, but to alleviate the need for another request after creation, I'd like to return the created model. I have a TwitterSerializer already which defines the fields that I want to include, so I'd like to be able to reuse this if possible.
TwitterSerializer
class TwitterSerializer(serializers.ModelSerializer):
class Meta:
model = Twitter
fields = (
"id",
"twitter_user_id",
"screen_name",
"display_name",
"profile_image_url",
)
When I try to use this, I get the error that Instance of TwitterSerializer is not JSON serializable.
serialized = TwitterSerializer(instance=twitter)
return JsonResponse({ "created": serialized })
I could return a serialized instance of the model using serializers.serialize()
serialized = serializers.serialize('json', [twitter, ])
serialized = serialized[0]
return JsonResponse({ "created": serialized })
I could pass the fields kwarg to serialize() but I don't want to have to repeat myself if I don't have to. So would it be possible to re-use my TwitterSerializer in this case? I'm having trouble finding a direct answer since most docs assume you'll be using a ViewSet when using serializerss understandably, and this feels like an edge case. I'm open to suggestions for refactoring this approach as well!
After serialization, you can get your data using data attribute of serializer like this.
serialized = TwitterSerializer(instance=twitter)
return JsonResponse({ "created": serialized.data })
You should use Django rest Response instead of JsonResponse like this
from rest_framework response
serialized = TwitterSerializer(instance=twitter)
return response.Response({ "created": serialized.data })

Django Rest Framework: Custom JSON Serializer

I am struggling to figure out how to write a custom serializer in DRF to parse a complex JSON data structure that is being passed to an endpoint. The JSON looks like this:
{
"name": "new",
"site": "US",
"data": {
"settings": [],
"meta": {
"meta1":{}
}
}
}
Here is my backend code:
# views.py
class SaveData(views.APIView):
def post(self, request, *args, **kwargs):
name = request.POST.get('name')
site = request.POST.get('site')
data = request.POST.get('data')
But data always returns None. On closer inspection into the request object, the incoming JSON looks like this:
# POST attribute of request object
'name' = 'new'
'site' = 'US'
'data[settings][0] = ''
'data[meta][meta1][] = ''
Basically it looks like the nested JSON objects associated with the data key are not getting properly serialized into Python dict and list objects. I've been looking for examples of custom DRF serializers but most of the ones I've found have been for serializing Django models, but I don't need to do that. The incoming data does not directly map to my models; instead I need to do some processing before I save any of the data.
Does anyone have any advice on a custom serializer that would properly convert the data JSON into proper Python objects? I started with this but it throws an exception (When a serializer is passed a 'data' keyword argument you must call '.is_valid()' before attempting to access the serialized '.data' representation.
You should either call '.is_valid()' first, or access '.initial_data' instead.). Here is my code with the custom serializer I created:
# serializers.py
class SaveDataSerializer(serializers.Serializer):
name = serializers.CharField()
site = serializers.CharField()
data = serializers.DictField()
def create(self, validated_data):
return dict(**validated_data)
Thanks.
I was able to solve my problem by converting the JS object to JSON when passing it to $.ajax, which then DRF was able to correctly parse into Python objects. Here's a snippet of the jQuery I'm using:
$.ajax({
url: '/api/endpoint',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(ajaxData),
done: function(data) {}
...
})
Note though that jQuery will default to x-www-urlencoded as the Content-Type if you don't specify anything. Since I'm using the JSON.stringify() method to explicitly convert the ajaxData object to a JSON string the Content-Type needed to be explicitly set as well.
I got the idea from this SO answer: Convert Object to JSON string
Of course, this doesn't really answer my original question, but this is another solution that is simpler and works like I wanted. By doing this I didn't have to create any custom serializers, but I did have to modify my API view like so:
class SaveData(views.APIView):
def post(self, request, *args, **kwargs):
name = request.data.get('name')
site = request.data.get('site')
data = request.data.get('data')
After doing this the expected object types are returned
>>> type(name)
str
>>> type(site)
str
>>> type(data)
dict

Categories