I have a ListAPIView that returns the json response below:
[
{'name': 'Andrew'},
{'name': 'Daniel'},
]
I want to alter it so that the response would look like:
{
"Users": {
[
{'name': 'Andrew'},
{'name': 'Daniel'},
]
}
}
How could I do that?
EDIT: Below are is my serializer and the View
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('name',)
class UserReadView(ListAPIView):
lookup_field = 'id'
serializer_class = UserSerializer
You can implement list method inside UserReadView and update response body inside it:
class UserReadView(ListAPIView):
lookup_field = 'id'
serializer_class = UserSerializer
def list(self, request, *args, **kwargs):
response = super().list(request, *args, **kwargs)
return Response({'Users':{response.data}})
Related
I built nested serializer where ModelSerializer include another serializer as field. Everything works well but in swagger docs in example body parameters I don't see openning_time field. What can I change to obtain openning_time field in docs? I tried with swagger_auto_schema but got error:
drf_yasg.errors.SwaggerGenerationError: specify the body parameter as a Schema or Serializer in request_body
serializers.py
class WarehouseSerializer(serializers.ModelSerializer):
openning_time = OpenningTimeSerializer(many=True, read_only=True)
class Meta:
model = Warehouse
fields = ['pk', 'name', 'action_available', 'openning_time', 'workers']
views.py
class WarehouseApi(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.ListModelMixin,
viewsets.GenericViewSet):
queryset = Warehouse.objects.all()
serializer_class = WarehouseSerializer
permission_classes = [IsAuthenticated, ]
warehouse_param_config = openapi.Parameter(
'openning_time', in_=openapi.IN_BODY, description='Description', type=openapi.TYPE_OBJECT)
#swagger_auto_schema(manual_parameters=[warehouse_param_config])
def update(self, request, *args, **kwargs):
return super().update(request, *args, **kwargs)
There is screen from swagger docs and i wanted add information about openning_time which is represented as list of dictionaries as below:
[
{
"weekday": 4,
"from_hour": "12:22:00",
"to_hour": "13:13:00"
},
{
"weekday": 5,
"from_hour": "16:00:00",
"to_hour": "23:00:00"
}
]
use decorator like
#swagger_auto_schema(request_body=WarehouseSerializer)
refer documentation
Custom schema generation
I am passing a value in my API POST request like this
{
"reason": "string"
}
And my view is like this,
class UpdateReason(GenericAPIView):
permission_classes = [IsAuthenticated]
serializer_class = serializers.ReasonSerializer
queryset = Food.objects.all()
def post(self, request, *args, **kwargs):
self.get_serializer().instance = service.update(self.get_object())
return Response({"success": True})
serializer.py
class ReasonSerializer(serializers.ModelSerializer):
class Meta:
model = Food
fields = ("id", "reason")
read_only_fields = ("id",)
In the post, I have to get the value of the reason and pass it to the service. How can I execute this?
Simply request.data.get('reason') :)
There seems to be a lot of documentation out there on this but none of it seems to work for me. I am trying to build an API View that creates one or many objects at the same time.
Something like passing in the following:
[
{
"response": "I have no favourites",
"user": 1,
"update": "64c5fe6f-cb65-493d-8ef4-126db0195c33",
"question": "297b46b4-714b-4434-b4e6-668ff926b38e"
},
{
"response": "Good",
"user": 1,
"update": "64c5fe6f-cb65-493d-8ef4-126db0195c33",
"question": "13916052-690e-4638-bb7c-908c38dcd75e"
}
]
My current Viewset
class FeedbackViewSet(viewsets.ModelViewSet):
permission_classes = [AllowAny]
queryset = Feedback.objects.all()
serializer_class = FeedbackSerializer
and Serializer:
class ContributionSerializer(serializers.ModelSerializer):
class Meta:
model = Contribution
fields = '__all__'
I have tried setting FeedbackSerializer(many=True) but this then tells me its not callable. Further, I have tried a ListCreateAPIView but this tells me it expects a dictionary but not a list.
you have the correct idea with many=True. You just need to put it in the correct location... so in the ViewSet:
class FeedbackViewSet(viewsets.ModelViewSet):
permission_classes = [AllowAny]
queryset = Feedback.objects.all()
serializer_class = FeedbackSerializer
def get_serializer(self, *args, **kwargs):
# add many=True if the data is of type list
if isinstance(kwargs.get("data", {}), list):
kwargs["many"] = True
return super(FeedbackViewSet, self).get_serializer(*args, **kwargs)
There are other ways to achieve the same behaviour, but I think this is pretty clean!
Override the create(...) method
from rest_framework.response import Response
from rest_framework import status
class FeedbackViewSet(viewsets.ModelViewSet):
permission_classes = [AllowAny]
queryset = Feedback.objects.all()
serializer_class = FeedbackSerializer
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data, many=True) # not that `many=True` id mandatory since you are dealing with a list of of inputs
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(
serializer.data,
status=status.HTTP_201_CREATED,
headers=headers
)
I have the following view:
class UserProfileView(APIView):
permissions_classes = [permissions.IsAuthenticated]
def get(self, request):
user = User.objects.get(id=request.user.id)
serializer = UserPrivateSerializer(user)
return Response(serializer.data)
The following Model:
class User(AbstractUser):
pp = models.ImageField(blank=True)
and the following serializer:
class UserPrivateSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
The following urls:
urlpatterns = [
path('profile/', UserProfileView.as_view())
]
I response I get is:
{
"pp": "/media/WIN_20190423_18_50_32_Pro.jpg"
}
when I expect:
{
"pp": "localhost:8000/media/WIN_20190423_18_50_32_Pro.jpg"
}
I know it's not a model or serializer issue because I have other views that use the same model and serializer where it returns the full path.
try this:
class UserProfileView(APIView):
permissions_classes = [permissions.IsAuthenticated]
def get(self, request):
user = User.objects.get(id=request.user.id)
serializer = UserPrivateSerializer(user, context=self.get_serializer_context())
return Response(serializer.data)
the key is add context=self.get_serializer_context() to you serializer.
It turns out all I do was to add context={"request": request} to the serializer.
I have the following two serializers:
class ProgramSerializer(serializers.ModelSerializer):
class Meta:
from radio.models import Program
model = Program
fields = ('id', 'title')
class UserRecentlyPlayedSerializer(serializers.ModelSerializer):
program_data = ProgramSerializer(read_only=True)
class Meta:
model = UserRecentlyPlayed
fields = ('id', 'user', 'program', 'program_data',)
They are based on the following models:
class Program(models.Model):
title = models.CharField(max_length=64)
class UserRecentlyPlayed(models.Model):
user = models.ForeignKey(User)
program = models.ForeignKey(Program)
What I'm trying to do is the following: On create, I want to be able create a new instance of UserRecentlyPlayed in the following manner:
{
"user": "...user id ....",
"program": "....program id...."
}
However, when I return a list, I would like to return the following:
[{
"id": "... id .... ",
"user": ".... user id .....",
"program": {"id": "...program id...", "title": "...title..." }
}]
These are called in the following view:
class RecentlyPlayed(generics.ListCreateAPIView):
serializer_class = UserRecentlyPlayedSerializer
This, unfortunately is not working. What is the correct magic for this?
You can rename your program_data in your serializer to program or you can specify source for your nested serializer.
That should return the output of list as you'd like.
class UserRecentlyPlayedSerializer(serializers.ModelSerializer):
program = ProgramSerializer(read_only=True)
class Meta:
model = UserRecentlyPlayed
fields = ('id', 'user', 'program',)
or
class UserRecentlyPlayedSerializer(serializers.ModelSerializer):
program_data = ProgramSerializer(read_only=True, source='program')
class Meta:
model = UserRecentlyPlayed
fields = ('id', 'user', 'program_data',)
And to support same json input for create, the easiest way is create another serializer for input:
class UserRecentlyPlayedSerializerInput(serializers.ModelSerializer):
program = serializers.PrimaryKeyRelatedField(queryset=Program.objects.all())
class Meta:
model = UserRecentlyPlayed
fields = ('id', 'user', 'program',)
And use it in your view when request is POST/PUT/PATCH:
class RecentlyPlayed(generics.ListCreateAPIView):
serializer_class = UserRecentlyPlayedSerializer
def get_serializer_class(self):
if self.request.method.lower() == 'get':
return self.serializer_class
return UserRecentlyPlayedSerializerInput
While this works great for a "get", I would like to see that same
result after a create. I still see {"program": "...id...."
For this, you have to change slightly the implementation of create method in your view
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
instance = serializer.save()
headers = self.get_success_headers(serializer.data)
oser = UserRecentlyPlayedSerializer(instance)
return Response(oser.data, status=status.HTTP_201_CREATED,
headers=headers)
Firstly create a property named program_data in your model
class Program(models.Model):
title = models.CharField(max_length=64)
class UserRecentlyPlayed(models.Model):
user = models.ForeignKey(User)
program = models.ForeignKey(Program)
#property
def program_data(self):
return self.program
Then in your serializer you do not need to change anything following, it will remain same as below
class ProgramSerializer(serializers.ModelSerializer):
class Meta:
from radio.models import Program
model = Program
fields = ('id', 'title')
class UserRecentlyPlayedSerializer(serializers.ModelSerializer):
program_data = ProgramSerializer(read_only=True)
class Meta:
model = UserRecentlyPlayed
fields = ('id', 'user', 'program', 'program_data',)
Ok, I went in a slightly different direction and it works. Instead of using the ListCreateAPIView, I created my own class using ListModeMixin, CreateModelMixin and GenericAPIView. The magic was in overriding the def list class. I also implemented a "return_serializer_class" attribute. That's what did it.
class RecentlyPlayed(mixins.ListModelMixin,
mixins.CreateModelMixin,
generics.GenericAPIView):
serializer_class = UserRecentlyPlayedSerializer
return_serializer_class = ProgramSerializer
parser_classes = (JSONParser, MultiPartParser)
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.create(request, *args, **kwargs)
return self.list(request, *args, **kwargs)
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.return_serializer_class(queryset, many=True)
return Response({'recently_played': serializer.data})