DRF serialize and save model with user FK - django

models.py
class UserContentItem(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=200)
created_date = models.DateTimeField(default=timezone.now)
views.py
class UserContentItemView(APIView):
permission_classes = (permissions.IsAuthenticatedOrReadOnly, )
def post(self, request, format=None):
data = request.data
data['owner'] = request.user.id
serializer = UserContentItemSerializer(data=data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
serializers.py
class UserContentItemSerializer(serializers.ModelSerializer):
class Meta:
model = UserContentItem
fields = ('id', 'owner', 'title', 'created_date')
I am building an API with Django Rest Framework and simple jwt. I want to allow authenticated users to POST a new UserContentItem that has a FK dependency on the User but the User is not part of the POST payload. The only way I've been able to figure out how to do this is as above, adding the request.user.id to the request data before passing it to the serializer. Is there a better way to serialize the UserContentItem and achieve the same goal?

I think you can try like this using CurrentUserDefault:
class UserContentItemSerializer(serializers.ModelSerializer):
owner = serializers.PrimaryKeyRelatedField(
read_only=True,
default=serializers.CurrentUserDefault()
)
class Meta:
model = UserContentItem
fields = ('id', 'owner', 'title', 'created_date')
And in view, pass request as context to serializer:
serializer = UserContentItemSerializer(data=data,context={'request':request})
Also you don't need to pass user id with data.

Your approach is good even you can follow these
serializer.save(owner=self.request.user)

Related

DRF-How to save foreign keys data using APIView

everyone, I am absolutely a beginner in DjangoRestFramework. I have confusion to deal with relationships in DRF. How do I save foreign keys data using APIView?
models
class User(AbstractUser):
email = models.EmailField(max_length=255, unique=True)
is_client = models.BooleanField(default=False)
is_professional = models.BooleanField(default=False)
class Client(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='client')
##
class Professionals(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='professional')
##
class HireProfessional(models.Model):
client = models.ForeignKey(Client, related_name='user', on_delete=models.CASCADE)
professional = models.ForeignKey(Professionals, on_delete=models.CASCADE, related_name="professsional")
hire_start_date_time = models.DateTimeField(default=timezone.now)
Serializers
class ProfessionalSerializer(serializers.ModelSerializer):
profilePicture = serializers.ImageField(allow_empty_file=True, use_url='professional/profiles', required=False)
skill = SkillSerializer(many=True,read_only=True)
class Meta:
model = Professionals
fields = fields = ['first_name', 'last_name', 'profilePicture', 'profession', 'phone_number', 'experience', 'skill', 'charge_fee', 'about_me']
class ClientSerializer(serializers.ModelSerializer):
class Meta:
model = Client
fields = ['user_id', 'first_name', 'last_name', 'phone_number', 'profilePicture']
class UserSerializer(serializers.ModelSerializer):
client = ClientSerializer()
professional = ProfessionalSerializer()
class Meta:
model = User
fields = ('email', 'username', 'is_client', 'is_professional', 'client', 'professional')
class HireProfessionalSerializer(serializers.ModelSerializer):
client = ClientSerializer()
professional = professionalSerializer()
class Meta:
model = HireProfessional
fields = ['id','client', 'professional', 'hire_start_date_time']
views ##Edited
class HireProfessionalCreateApp(APIView):
permission_classes = (IsAuthenticated, IsClientUser,)
def current_user(self):
user = self.request.user.client
return user
def post(self, request, username, format=None):
try:
professional = Professionals.objects.get(user__username=username)
# print('hello', professional.user_id)
data = request.data
data['client'] = self.current_user()
data['professional'] = professional.user_id
serializer = HireProfessionalSerializer(data=data)
data = {}
if serializer.is_valid():
hire = serializer.save()
hire.save()
return JsonResponse ({
"message":"professional hired success.",
# "remaning_time":remaning_datetime,
"success" : True,
"result" : serializer.data,
"status" : status.HTTP_201_CREATED
})
else:
data = serializer.errors
print(data)
return Response(serializer.data)
except Professionals.DoesNotExist:
return JsonResponse ({"status":status.HTTP_404_NOT_FOUND, 'message':'professional does not exists'})
This is a hiring app.
Client able to hire to professional
client=logged in user
professional=passed id or username through URL
like: path('hire-professional/<professional id or username>', views)
Does anybody have any idea? how to solve it.
Consider using a ModelViewSet instead of an APIView since you're directly modifying the HireProfessional model. Additionally, the model uses ForeignKey fields for Client and Professional, you do not need to include their respective serializers in HireProfessionalSerializer.
Implementing just the ModelViewSet (and adding it to the urls.py using a router) will mean that a user can select the client & professional themself, which means we're not done yet. It is recommended to use a router in DRF instead of adding all the views to urls.py manually.
You can use the ModelViewSet to override the perform_create and perform_update functions in which you autofill serializer fields. :
default create function
def perform_create(self, serializer):
serializer.save()
autofill example
def perform_create(self, serializer):
# client is derived from logged in user
client = Client.objects.get(user=self.request.user)
# Autofill FK Client.
serializer.save(client=client)
Now the client is autofilled, but the professional isn't yet. If you want to also autofill the professional you will have to look into using a nested router since you want to retrieve the PK(id) from the URL. If you do so your URL would look something like this:
url='/professionals/{professionals_pk}/hire/'
I hope this gives you an idea of where to start. If you have any questions do let me know.

How to post array data in DRF

When I post to array json like {"productId":[1, 2, 3]}.
I got errors
Cannot assign "[<Product: Short Sleeve>, <Product: Short Sleeve>, <Product: Short Sleeve>]": "FittingCartItem.productId" must be a "Product" instance.
I already try add many=True argument in get_serializer function.
I don't know how solve this problem...
serializers.py
class ItemListSerializer(serializers.ModelSerializer):
product = ProductSerializer(source='productId', read_only=True)
memberId = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), write_only=True, required=False)
productId = serializers.PrimaryKeyRelatedField(queryset=Product.objects.all(), write_only=True, many=True)
is_live = serializers.BooleanField(default=True)
class Meta:
model = FittingCartItem
fields = '__all__'
views.py
class ItemListView(generics.ListCreateAPIView):
serializer_class = ItemListSerializer
queryest= FittingCartItem.objects.all()
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
serializer.save()
headers = self.get_success_headers(serializer.data, many=True)
return Response(serializer.data, status=status.HTTP_201_CREATED,
headers=headers)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
models.py
class FittingCartItem(models.Model):
memberId = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='fittingcarts')
productId = models.ForeignKey(Product, on_delete=models.CASCADE)
created_time = models.DateTimeField(auto_now_add=True)
is_live = models.BooleanField(default=True)
In yout serializer, you want your product to be serialized by ProductSerializer, which causes your serializer to expect "Product" instances instead of productIds in your JSON input.
What you need is to tell yout serializer to expect products as productIds, which is possible by using PrimaryKeyRelatedField.
You need to delete:
product = ProductSerializer(source='productId', read_only=True)
This will solve your problem because DRF automatically acts ForeignKeys in models as PrimaryKeyRrlatedFields. But if productId was not defined as ForeignKey, but instead ManyToMany relation etc. You need to add it as below:
productId = PrimaryKeyRelatedField(many=True, read_only=True)

django rest framework - POST request causes 400 status code

I am trying to perform a POST request to create an article and I am getting this error Request failed with status code 400 Bad Request: /api/articles/create/.
An article needs 3 attributes to be created:
(1) title
(2) body
(3) author (the current user)
The router works fine since the POST request goes into the post method of the ArticleCreateView class. But I'm guessing that serializer.is_valid() is returning False for some reason.
Also print(request.data) returns {'title': 'hello', 'body': 'stuff'}
Another mystery about Django is, how does the serializer know if I want to get, create or update something? In all the examples I've seen, the serializer magically seems to know this.
class ArticleCreateView(CreateAPIView):
permission_classes = [IsAuthenticated]
def post(self, request):
serializer = ArticleSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors)
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = '__all__'
def create(self, validated_data):
author = self.context['request'].user
title = validated_data.get('title')
body = validated_data.get('body')
return Article.objects.create(author=author, title=title, body=body)
class Article(models.Model):
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
body = models.TextField(max_length=100)
date_updated = models.DateTimeField(auto_now=True)
date_created = models.DateTimeField(auto_now_add=True)
According to your serializer, the validation process needs author details, which is not passing through POST payload.
So, make the author field in the serializer a not required field by adding required=False or use a ReadOnlyField() or specify read_only_fields in Meta class. Also pass context data as #nishant mentioned
# views.py
class ArticleCreateView(CreateAPIView):
permission_classes = [IsAuthenticated]
def post(self, request):
serializer = ArticleSerializer(data=request.data, context={"request": request}) # change here
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors)
# serializer.py
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = '__all__'
read_only_fields = ('author',)
def create(self, validated_data):
author = self.context['request'].user
title = validated_data.get('title')
body = validated_data.get('body')
return Article.objects.create(author=author, title=title, body=body)
add this
serializer = ArticleSerializer(data=request.data, context={'request':request})

How to make change in request.data keeping check on serializer validataion in django rest framework?

So I am making quiz app which has following structure
class UserQuizRecord(models.Model):
user = models.OneToOneField(User)
quiz = models.ForeignKey(Quiz, on_delete=models.CASCADE, related_name='userrecord')
score = models.FloatField(default=0.00)
I have serializer class of it
class UserQuizRecordSerializer(serializers.ModelSerializer):
class Meta:
model = UserQuizRecord
fields = ('user', 'quiz','score')
Here is the views.py file and the detail route I am providing with data {quiz:1,score:7} I wish to add user to the request.data or serializer at in the views function. I am failing to get the right way to do it.
class QuizView(viewsets.ModelViewSet):
serializer_class = QuizSerializer
queryset = Quiz.objects.all()
model = Quiz
#detail_route(methods=['post'], permission_classes=[AllowAny], url_path='rank')
def rank(self, request, pk=None):
request.data.quiz = Quiz.objects.get(pk=pk)
serializer = UserQuizRecordSerializer(data = request.data )
if serializer.is_valid():
serializer.save()
rank = UserQuizRecord.objects.filter(Q(quiz=request.data.quiz.id),Q(score__gte=request.data.score)).count()
return Response({'rank':rank}, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
What is the best way to perform this operation?

Django Rest Framework return nested object using PrimaryKeyRelatedField

I am using DRF to expose some API endpoints.
# models.py
class Project(models.Model):
...
assigned_to = models.ManyToManyField(
User, default=None, blank=True, null=True
)
# serializers.py
class ProjectSerializer(serializers.ModelSerializer):
assigned_to = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), required=False, many=True)
class Meta:
model = Project
fields = ('id', 'title', 'created_by', 'assigned_to')
# view.py
class ProjectList(generics.ListCreateAPIView):
mode = Project
serializer_class = ProjectSerializer
filter_fields = ('title',)
def post(self, request, format=None):
# get a list of user.id of assigned_to users
assigned_to = [x.get('id') for x in request.DATA.get('assigned_to')]
# create a new project serilaizer
serializer = ProjectSerializer(data={
"title": request.DATA.get('title'),
"created_by": request.user.pk,
"assigned_to": assigned_to,
})
if serializer.is_valid():
serializer.save()
else:
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
return Response(serializer.data, status=status.HTTP_201_CREATED)
This all works fine, and I can POST a list of ids for the assigned to field. However, to make this function I had to use PrimaryKeyRelatedField instead of RelatedField. This means that when I do a GET then I only receive the primary keys of the user in the assigned_to field. Is there some way to maintain the current behavior for POST but return the serialized User details for the assigned_to field?
I recently solved this with a subclassed PrimaryKeyRelatedField() which uses the id for input to set the value, but returns a nested value using serializers. Now this may not be 100% what was requested here. The POST, PUT, and PATCH responses will also include the nested representation whereas the question does specify that POST behave exactly as it does with a PrimaryKeyRelatedField.
https://gist.github.com/jmichalicek/f841110a9aa6dbb6f781
class PrimaryKeyInObjectOutRelatedField(PrimaryKeyRelatedField):
"""
Django Rest Framework RelatedField which takes the primary key as input to allow setting relations,
but takes an optional `output_serializer_class` parameter, which if specified, will be used to
serialize the data in responses.
Usage:
class MyModelSerializer(serializers.ModelSerializer):
related_model = PrimaryKeyInObjectOutRelatedField(
queryset=MyOtherModel.objects.all(), output_serializer_class=MyOtherModelSerializer)
class Meta:
model = MyModel
fields = ('related_model', 'id', 'foo', 'bar')
"""
def __init__(self, **kwargs):
self._output_serializer_class = kwargs.pop('output_serializer_class', None)
super(PrimaryKeyInObjectOutRelatedField, self).__init__(**kwargs)
def use_pk_only_optimization(self):
return not bool(self._output_serializer_class)
def to_representation(self, obj):
if self._output_serializer_class:
data = self._output_serializer_class(obj).data
else:
data = super(PrimaryKeyInObjectOutRelatedField, self).to_representation(obj)
return data
You'll need to use a different serializer for POST and GET in that case.
Take a look into overriding the get_serializer_class() method on the view, and switching the serializer that's returned depending on self.request.method.