How to add user in perform_create viewset (django rest framework)? - django-rest-framework

I want to make a "comment" table. On the table, there will be a field containing user_id that created the comment. The user related with the comment table is from Django default user model. This is the Comment model:
class Comment(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
content = models.TextField()
pub_date = models.DateTimeField(auto_now_add=True)
This is the Comment serializer:
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = '__all__'
This is the viewset to create new comment:
class CommentViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
permission_classes = (IsAuthenticated,)
def perform_create(self, serializer):
serializer.save(user=self.request.user)
I also integrate the user with django rest jwt for the authorization. This is the header & data i sent to the API.
Header:
Authorization: Bearer {jwt token}
Content-Type: application/json
body:
{
"content": "This is the comment"
}
But i get this response:
{
"user": [
"This field is required."
]
}
How to fix the serializer, so it will retrieve the user from the token (request.user)?

This is because of the user field isn't a read only field. So you have to make it so.
This DRF doc -- Specifying read only fields describes well about it
In your case, specifying read_only_fields in your serializer's Meta class will solve the problem :)
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = '__all__'
read_only_fields = ('user',) # change is here <<<

Related

drf-spectacular: how to show the primary key in examples section of Swagger

I'm trying to show the primary key in the examples section of Swagger, I'm using drf-spectacular and my code looks like:
Serializers.py
class SerializerExample(serializers.ModelSerializer):
class Meta:
model = Book
fields = ('id','name')
Views.py
class BooksBulkUpdate(APIView):
#extend_schema(
request=SerializerExample(many=True),
responses={200:''},
)
def put(self, request, format=None):
with transaction.atomic():
for data in request.data:
book = Book.objects.get(pk=data['id'])
serializer = SerializerExample(book, data=data, partial=True)
if serializer.is_valid():
serializer.save()
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
return Response()
Only the name field is showing:
The only solution that I found was using an inline serializer which is not the ideal solution because if I update my book serializer I'd have to remember to update also this inline serializer. I wonder if there is a better way of doing this.
AFAIK swagger shows input request schema.
For example, you want to add new person and your model is
class Person(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=30)
So you allowed to set only name parameter
Even if you post
{
"id": "someUUID",
"name": "NAME",
}
id will be ignored and Django create it automatically by own logic (because it is read only)
But you can set id field writeable:
class SerializerExample(serializers.ModelSerializer):
id = serializers.UUIDField(write_only=True)
name = serializers.CharField(write_only=True)
class Meta:
model = Person
fields = ('id','name')
write_only=True means that field will be active when you saving new data and receiving id from request json.
In opposite read_only=True will print id field at response (if you trying get data) but ignore it when you saving new data.
So you try to describe API for data adding, and of course that is not allow to set id field in request json.
Not sure if this theory applicable to your case, but hope that will be helpful.

Unable to save with two models id field

Groom can like Bride, and Bride can like Groom. These two are different models in Likes Model.
I'm using the view set. Able to save individual model data but when I save Likes DRF throws field required error. Please suggest how to do better handling of many to many relationship handling between two models.
I also tried with ForeignKey relation in the models. The from bride/groom would be enforced with logged-in user id that is register bride/groom id (pk).
Models
class RegisterBride(models.Model):
'''Register Bride/Female profiles.'''
fname = models.CharField(max_length=200)
lname = models.CharField(max_length=200)
mobile = models.IntegerField(default=None)
gender = models.CharField(max_length=30, default='Female')
age = models.PositiveIntegerField(default=None, validators=[ MinValueValidator(18), MaxValueValidator(100)])
bride_account_status = models.BooleanField(default=False)
def __str__(self):
return self.fname + ' '+self.lname
class RegisterGroom(models.Model):
fname = models.CharField(max_length=200)
lname = models.CharField(max_length=200)
mobile = models.IntegerField(default=None)
gender = models.CharField(max_length=30, default='Male')
age = models.PositiveIntegerField(default=None, validators=[ MinValueValidator(18), MaxValueValidator(100)])
groom_account_status = models.BooleanField(default=False)
def __str__(self):
return self.fname + ' '+self.lname
class Likes(models.Model):
bride_profile = models.ManyToManyField(RegisterBride, related_name='bride_profile')
groom_profile = models.ManyToManyField(RegisterGroom, related_name='groom_profile')
likes = models.BooleanField(default=False)
created_date = models.DateField(auto_now_add=True)
Views
class RegisterGroomProfileViewSet(viewsets.ModelViewSet):
queryset = RegisterGroom.objects.all()
serializer_class = RegisterGroomSerializer
class RegisterBrideProfileViewSet(viewsets.ModelViewSet):
queryset = RegisterBride.objects.all()
serializer_class = RegisterBrideSerializer
class LikesViewSet(viewsets.ModelViewSet):
queryset = Likes.objects.all()
serializer_class = LikesSerializer
Serializers
class RegisterGroomSerializer(serializers.ModelSerializer):
class Meta:
model = RegisterGroom
fields = '__all__'
class RegisterBrideSerializer(serializers.ModelSerializer):
class Meta:
model = RegisterBride
fields = '__all__'
class LikesSerializer(serializers.ModelSerializer):
groom_profile_s = RegisterGroomSerializer(many=True, required=True)
bride_profile_s = RegisterBrideSerializer(many=True, required=True)
class Meta:
model = Likes
fields = '__all__'
extra_kwargs = {'likes': {'required': True}}
def create(self, data):
if data['bride_profile'] == data['groom_profile']:
raise ValidationError(detail='To Profile ID == From Profile ID. They should be different.')
return data
Request
Request URL: http://127.0.0.1:8000/likes/
Request Method: POST
Status Code: 400 Bad Request
{'likes': True,
'bride_profile': 1,
'groom_profile': 6}
Response
POST /likes/
HTTP 400 Bad Request
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
{
"groom_profile_s": [
"This field is required."
],
"bride_profile_s": [
"This field is required."
]
}
Server Error:
..\rest_framework\pagination.py:200: UnorderedObjectListWarning: Pagination may yield inconsistent results with an unordered object_list: <class 'register.models.Likes'> QuerySet.
paginator = self.django_paginator_class(queryset, page_size)
Please suggest me.
Your request is not good : you put groom_profile_s and bride_profile_s as required but you do not send them in your request !
Request URL: http://127.0.0.1:8000/likes/
Request Method: POST
Status Code: 400 Bad Request
{'likes': True,
'groom_profile_s': [1],
'bride_profile_s': [6]}
After modifying your query, you should update your serializer.
class LikesSerializer(serializers.ModelSerializer):
groom_profile_s = RegisterGroomSerializer(many=True, required=True)
bride_profile_s = RegisterBrideSerializer(many=True, required=True)
class Meta:
model = Likes
fields = '__all__'
extra_kwargs = {'likes': {'required': True}}
def create(self, data):
if 'groom_profile_s' in data.keys():
groom_profile_s = validated_data.pop('groom_profile_s')
// Deal with your data here by creating the Like
// Same for bride_profile_s

How to restrict fields when creating post request in DRF?

I am making a POST api using DRF. In that api, I need only few fields(name, size, customer_name, customer_address), but don't require this fields(status, ordered_time) because these fields I want to save these fields in run time as status='open' and ordered_time=DateTimeField.now()
views.py
class PizzaOrderCustomerView(APIView):
def post(self, request):
orders = request.data.get('orders')
# Create an article from the above data
serializer = ArticleSerializer(data=orders)
if serializer.is_valid(raise_exception=True):
article_saved = serializer.save()
return Response({"success": "Article '{}' created successfully".format(article_saved.name)})
models.py
class PizzaOrder(models.Model):
name = models.CharField(max_length=120)
size = models.CharField(max_length=10, choices=SIZE_CHOICE, default='MEDIUM')
customer_name = models.CharField(max_length=120)
customer_address = models.TextField()
ordered_time = models.DateTimeField(default=timezone.now, editable=False)
status = models.CharField(max_length=20, default='open', editable=False)
serializers.py
class OrderSerializer(serializers.ModelSerializer):
class Meta:
model = PizzaOrder
# fields = '__all__'
read_only_fields = ('status',)
But when I try to create an order, it needed status and ordered_time also. But it should save at the time of creating order automatically.
Suggest a good way to do it.
from rest_framework import viewsets, mixins
class PizzaViewsets(viewsets.ViewSet, mixins.CreateModelMixin):
model = PizzaOrder
serializer_class = OrderSerializer
queryset = model.objects.all(
serializer, it is always good practise to mention all fields instead of
all
class OrderSerializer(serializers.ModelSerializer):
class Meta:
model = PizzaOrder
fields = ('status','ordered_time','name', 'size', 'customer_name', 'customer_address',)
read_only_fields = ('status','ordered_time',)

Change model field if requested particular url

Im making API using django rest framework . I only want to change one field in model which is the read field if i go to a particular url
my model:
class Notification(PolymorphicModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
created_by = models.ForeignKey(ElsUser, on_delete=models.CASCADE, default=None, related_name="creatednotifications")
created_on = models.DateTimeField(default=timezone.now)
created_for = models.ForeignKey(ElsUser, on_delete=models.CASCADE, default=None, related_name="receivednotifications")
read = models.DateTimeField(default=None, null=True, blank=True)
message = models.CharField(default=None, blank=True, null=True, max_length=800)
The APis i made lists the notifications for a logged in user.
What i want to implement is that :
notification/<:id>/markread
notification/<:id>/markunread
If i go to this particular url i want to modify the read field ..For example make it None if to mark unread. Also i need to check if the logged in user has received the notification with that id.I know the basics and how to create the urls
class NotificationMarkRead(generics.UpdateAPIView):
serializer_class = NotificationSerializer
def get_queryset(self):
queryset = Notification.objects.filter(created_for=self.request.user)
return queryset
class NotificationMarkUnread(generics.UpdateAPIView):
serializer_class = NotificationSerializer
def get_queryset(self):
queryset = Notification.objects.filter(created_for=self.request.user)
return queryset
def update
My initial try is to override the put method in update_API view
Write a simple function:
#api_view(['PUT'])
def notification_toggle_read_status(request, pk, read_status):
notification = Notification.objects.get(pk=pk)
if read_status == 'markread':
notification.read = timezone.now()
else:
notification.read = None
notification.save(update_fields=['read'])
serializer = NotificationSerializer(instance=notification)
return Response(serializer.data)
use this url path:
notifications/<int:pk>/<string:read_status>/
As you have already coding with DRF why not try with viewset link . And from front-end just pass update fields with put request.

Handling viewset that has a serializer with multiple foreignkeys

I have this serializer:
class ReviewSerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=True)
class Meta:
model = Review
fields = ('pk', 'title', 'user', 'movie', 'timestamp', 'review_text',)
I want to mention that user and movie are both ForeignKeys defined in models.py for Review model. Here is the model:
class Review(models.Model):
title = models.CharField(max_length=255)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='reviews')
movie = models.ForeignKey(Movie, on_delete=models.CASCADE, related_name='reviews')
review_text = models.TextField()
timestamp = models.DateTimeField(auto_now_add=True)
def __str__(self):
return '{movie} review by {user}'.format(user=self.user, movie=self.movie)
I want to create a viewset, but if I do something like this:
class ReviewsViewSet(viewsets.ModelViewSet):
queryset = Movie.objects.all()
serializer_class = ReviewSerializer
and register it like this:
router.register(r'reviews', views.ReviewsViewSet, 'reviews')
won't work, it requires me to provide a movie field as logged here:
AttributeError at /api/reviews/
Got AttributeError when attempting to get a value for field `movie` on serializer `ReviewSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `Movie` instance.
Original exception text was: 'Movie' object has no attribute 'movie'.
Thanks in advance.
Edited.

Resources