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') :)
Related
I have to create an endpoint to make a customer subscribes to a chef.
The subscription model has two FK fields chef and customer.
model/subscriptionSerializer:
class Subscription(models.Model):
chef = models.ForeignKey(Chef, on_delete=models.CASCADE, db_column="chef")
customer = models.ForeignKey(User, on_delete=models.CASCADE, db_column="customer")
I created this serializer:
class SubscriptionSerializer(serializers.ModelSerializer):
chef = serializers.SerializerMethodField("get_chef")
customer = serializers.SerializerMethodField("get_customer")
class Meta:
model = Subscription
fields = [
"id",
"chef",
"customer",
]
def get_chef(self, *args):
return Chef.objects.get(pk=self.context.get("chef"))
def get_customer(self, *args):
return User.objects.get(pk=self.context.get("customer"))
I want to have one endpoint like this: customer/chef/<int:chef_id>/subscribe
So in view.py I used ModelViewSet:
class CustomerSubscribeChefView(viewsets.ModelViewSet):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = SubscriptionSerializer
def get_serializer_context(self):
context = super(CustomerSubscribeChefView, self).get_serializer_context()
context.update({"chef": self.kwargs['chef_id']})
context.update({"customer": self.request.user.id})
return context
def get_queryset(self):
return self.request.user.subscription_set
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
return Response(
{"msg": "Chef is subscribed!"}, status=status.HTTP_201_CREATED
)
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
self.perform_destroy(instance)
return Response({"msg": "Chef is unsubscribed!"}, status=status.HTTP_200_OK)
when I try to send a post request I got this error:
django.db.utils.IntegrityError: NOT NULL constraint failed: core subscription. chef _id
I've tried to use my custom serializer so I've updated SubscriptionSerializer to be like this:
class SubscriptionSerializer(serializers.ModelSerializer):
chef = ChefListSerializer
customer = CustomerSerializer
class Meta:
model = Subscription
fields = [
"id",
"chef",
"customer",
]
But I don't know how to serialize chef and customer objects.
What is the problem with my approach, and how I can fix this?
Update
I changed the subscription serializer to be like this
class SubscriptionSerializer(serializers.ModelSerializer):
chef = Chef()
customer = User()
def __init__(self, *args, **kwargs):
context = kwargs.pop('context')
self.chef = Chef.objects.get(pk=context.get("chef"))
self.customer = User.objects.get(pk=context.get("customer"))
super().__init__(*args, **kwargs)
class Meta:
model = Subscription
fields = ["id", "chef", "customer"]
But I still get the same error.
I have an API endpoint that supports the POST method which works if I include the user in the request payload.
I would like to eliminate this from the request payload and have DRF just use the logged in user.
I am getting the following error when I omit the user from the request body:
HTTP 400 Bad Request
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
{
"user": [
"This field is required."
]
}
This appears to be coming from when the framework calls serializer.is_valid().
How do I configure DRF to populate the user from request.user so that serializer validation doesn't fail?
models.py
class Task(models.Model):
created = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
domain = models.CharField(max_length=settings.MAX_CHAR_COUNT)
serializers
class TaskSerializer(serializers.HyperlinkedModelSerializer):
user = serializers.SlugRelatedField(queryset=CustomUser.objects.all(), slug_field='email')
class Meta:
model = Task
fields = ['id', 'created', 'domain', 'user']
views.py
class TaskApiViewSet(viewsets.ModelViewSet):
permission_classes = [permissions.IsAuthenticated,]
serializer_class = TaskSerializer
task_service = TaskService()
"""
GET /api/tasks
"""
def get_queryset(self):
user = self.request.user
if user.is_superuser:
return Task.objects.all()
return Task.objects.filter(user=self.request.user)
"""
POST /api/tasks
"""
def create(self, request):
domain = request.data['domain']
can_create_task = self.task_service.can_create_task(user=request.user, domain_name=domain)
if not can_create_task:
raise PermissionDenied(code=None, detail=None)
return super().create(request)
The correct method of doing this, according to Django Rest Framework, is to override the perform_create() method in your ViewSet, which is responsible of creating an instance of a model using a serializer. You can pass additional data to the serializer:
def perform_create(self, serializer):
serializer.save(user=self.request.user)
The original definition of the perform_create() method can be found here.
You might need to completely override your ViewSet's create() method to be able to pass a modified dict to the serializer. You can just copy the original definition
Afterwards you can add the user to the request data by making a copy, modifying it, and passing it to the serializer:
def create(self, request, *args, **kwargs):
user_id = self.request.user.id
domain = request.data['domain']
new_data = request.data.copy()
new_data.update({"user": user_id})
can_create_task = self.task_service.can_create_task(user=request.user, domain_name=domain)
if not can_create_task:
raise PermissionDenied(code=None, detail=None)
# original definition of create() with new_data
serializer = self.get_serializer(data=new_data)
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)
A simple solution to your problem would be using the "extra_kwargs" field inside your Meta class,for your case it would be something like this:
class Meta:
model = Task
fields = ['id', 'created', 'domain']
extra_kwargs = {'user': {'default': serializers.CurrentUserDefault()}}
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 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})
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}})