How to set authenticated user to serializer? - python

I created simple CreateAPIView using django rest framework.
class CreatePostAPIView(generics.CreateAPIView):
serializer_class = serializers.PostSerializer
permission_classes = [IsAuthenticated]
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = '__all__'
class Post(models.Model):
user = models.ForeignKey(User, on_delete = models.CASCADE, null = True, blank = False)
spot = models.ForeignKey(Spot, on_delete = models.CASCADE, null = True, blank = False)
comment = models.TextField(null = True, blank = False)
Model Spot has three fields
user
spot
comment
From client, spot and comment are sent using http POST request param as from of JSON.
However user does not come with http POST request param but I need to get user from django's authenticated user(like request.user). I know I can get user from request.user but how can I set the user to the serializer?

Override the perform_create(...)--(DRF doc) method of CreatePostAPIView class and set user in read_only_fields--(DRF doc) in PostSerializer class
# views.py
class CreatePostAPIView(generics.CreateAPIView):
serializer_class = serializers.PostSerializer
permission_classes = [IsAuthenticated]
def perform_create(self, serializer):
serializer.save(user=self.request.user)
# serializers.py
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = '__all__'
read_only_fields = ('user',)

Related

How to post with references in Django Rest Framework

I have Comment model related with User model
# models.py
class Comment(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
more fields...
....
In the serializer I want to do a create (POST) of a user comment.
But the post method is not enabled, only the put or patch method
Example: User Jon wants to create a comment
# serializers.py
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = '__all__'
class UserCommentSerializer(serializers.ModelSerializer):
# id of comment
url = serializers.HyperlinkedIdentityField(
view_name="user-comments-detail",
read_only=True
)
id = serializers.CharField(read_only=True)
comment = CommentSerializer()
class Meta:
model = User
fields = ['id', 'comment']
def create(self, validated_data):
comment_data = validated_data.pop('comment')
user = User.objects.create(**validated_data)
Comment.objects.create(user=user, **comment_data)
return user
I want to new comment, referencing the user
# views.py
class CommentViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserCommentSerializer
But I have an exception, that the user has no comment RelatedObjectDoesNotExist
My url like this
http://localhost:8000/user-comments/10
10 is a user_id pk
{} object post
Example: Comment.objects.create(user=pk, {})
Currently, only put and patch is enabled, but what I want to do is post of user
{
"url": "http://localhost:8000/user-comments/10",
"id": "10",
"comment": null
}
Comment does not exist
Any idea or suggestion?
You actually need just one serializer for that.
This will create a comment for the current logged in user.
# serializers.py
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = '__all__'
read_only_fields = ['user']
def create(self, validated_data):
# get the user who sent the request
user = self.context['request'].user
return Comment.objects.create(user=user, **validated_data)
# views.py
class CommentViewSet(viewsets.ModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
def get_serializer_context(self):
# this is important since you want to pass the request object to your serializer
context = super().get_serializer_context()
context.update({"request": self.request})
return context
The exception you are getting is because ModelSerializer is linked to a specific model, in this case you linked UserCommentSerializer to model User. Variable Meta.fields specifies which fields of the model are being returned by the serializer and, so, you are getting an exception because such variable is set to ('id', 'comment') and model User doesn't have a field comment.
You can achieve what you want, this way:
class UserCommentSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id']
extra_kwargs = {'comment': {'write_only': True}}
def create(self, validated_data):
comment_data = validated_data.pop('comment')
user = User.objects.create(**validated_data)
Comment.objects.create(user=user, comment=comment_data)

Update a field value in model Django Rest

serializers.SerializerMethodField() is only meant to read value ie
retrieve, it will not update the lang value.
I want to create an API that can update a field value. Mainly, I want to change the field lang in the User model. Here is my code
models.py
class Profile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, related_name='profile', on_delete=models.CASCADE)
member_since = models.DateTimeField(default=timezone.now)
lang = models.CharField(max_length = 5, default = 'en')
def get_lang(self):
return self.lang
serializers.py
from django.contrib.auth import get_user_model
User = get_user_model()
class ProfileRetrieveUpdateSerializer(serializers.ModelSerializer):
member_since = serializers.SerializerMethodField()
lang = serializers.SerializerMethodField()
class Meta:
model = User
fields = [
'member_since','lang'
]
def get_member_since(self, obj):
return obj.profile.member_since.date()
def get_lang(self, obj):
return obj.profile.get_lang()
def update(self, instance, validated_data):
print(validated_data)
setattr(instance.profile, "lang", validated_data.get("lang",instance.profile.lang))
instance.save()
return instance
views.py
from django.contrib.auth import get_user_model
from .serializers import (
CurrentUserDetailSerializer, ProfileRetrieveUpdateSerializer,
UserLoginSerializer, UserSerializerWithToken,
)
User = get_user_model()
class ProfileRetrieveUpdateAPIView(RetrieveUpdateAPIView):
"""
View that returns user profile data.
"""
permission_classes = [AllowAny]
queryset = User.objects.all()
serializer_class = ProfileRetrieveUpdateSerializer
lookup_field = 'username'
lookup_url_kwarg = 'username'
axios.patch(url, {'lang': 'fr') (additional, I don't think it's wrong here)
When I call API, I get that the validated_data value is {}!
I think the problem is in the serializers.py module.
lang = serializers.CharField(source = 'get_lang')
def update(self, instance, validated_data):
setattr(instance.profile, "lang", validated_data.get("get_lang"))
instance.save()
return instance
reason: serializers.SerializerMethodField() is only meant to read value ie retrieve, it will not update lang value.

how to merge models in django rest framework in one serializer class

i am trying to add CommentSerializer class to write comments with PostListSerializer , i want to display comment field bottom at every exist post
django:2.2
models.py
class Comment(models.Model):
id = models.UUIDField(default=uuid.uuid4,primary_key=True,editable=False)
author = models.ForeignKey(Account,on_delete=models.CASCADE,related_name='comments')
post= models.ForeignKey(Post,on_delete=models.CASCADE,related_name='comments')
comment_content = models.TextField(max_length=400)
commented_at = models.DateTimeField(auto_now_add=True)
edited_at = models.DateTimeField(auto_now=True)
image = models.ImageField(upload_to=random_url,blank=True,null=True)
parent = models.ForeignKey('self',null=True,blank=True,on_delete=models.CASCADE,related_name='replies')
active = models.BooleanField(default=True)
this is the serializers.py
class CommentCreateSerialzier(ModelSerializer):
class Meta:
model = Comment
fields = [
'comment_content','image'
]
class TripListSerializer(TaggitSerializer, GeoFeatureModelSerializerGIS):
tags = NewTagListSerializerField()
comments = SerializerMethodField()
detail_url = post_detail_url
user= SerializerMethodField()
class Meta:
model = Post
geo_field = 'location'
fields = [
'detail_url','user','name','city','tags','comments'
]
def get_user(self,obj):
return str(obj.user.username)
def get_comments(self,obj):
comment = Comment.objects.filter(post=obj,active=True)
comments = CommentSerialzier(comment,many=True).data
return comments
and this is my views.py
class CommentCreateSerializer(CreateAPIView):
serializer_class = CommentCreateSerialzier
message = 'you dont have permission to comment'
permission_classes = [IsAuthenticated]
def perform_create(self):
serializer.save(author=self.request.username)
how to add default post object in every single post ? and does it the same for replies as well
thanks for replying

Filtering querysets for nested models

I'm making a News App API and I want to create an APIView with comments for a speicific Post that also lets users posting comments for the specific post.
These are my models (simplified):
Post:
class Post(models.Model):
title = models.CharField(max_length=250)
text = models.TextField()
Comment:
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
author = models.CharField(max_length=200)
text = models.TextField()
And view:
class CommentList(generics.ListCreateAPIView):
queryset = Comment.objects.filter(post=???)
serializer_class = CommentSerializer
EDIT: I would also like my url path to look like this (or similar):
urlpatterns = [
...
path('posts/<int:pk>/comments/', CommentList.as_view())
]
My questions:
How do I create a list of comments for an instance of Post?
Is it the correct approach or should I try something else?
If your url for comments is something like: /posts/post_id/comments/
# serializer
class CommentSerializer(serializers.ModelSerializer):
author = SomeAuthorSerializer(read_only=True)
class Meta:
model = Comment
fields = ('author', 'text')
# view
class CommentViewSet(viewsets.GenericViewSet,
mixins.CreateModelMixin,
mixins.ListModelMixin):
queryset = Comment.objects
serializer_class = CommentSerializer
def initial(self, request, *args, **kwargs):
super().initial(request, *args, **kwargs)
try:
self.post = Post.objects.get(pk=self.request.query_params.get('post_id'))
# Prefetch post object in this situation let you check permissions
# eg.:
if self.post.author != self.request.user:
raise PermissionDenied()
# remember that permission classes should be used !
except Post.DoesNotExist:
raise NotFound()
# It will filter your comments
def filter_queryset(self, queryset):
queryset = queryset.filter(post=self.post)
return super().filter_queryset(queryset)
# It will automatically bypass post object and author
# to your serializer create method
def perform_create(self, serializer):
serializer.save(post=self.post, author=self.request.user)
This is the solution that worked for me:
class CommentByPostList(generics.ListCreateAPIView):
queryset = Comment.objects.all()
serializer_class = CommentListSerializer
def get_queryset(self):
return Comment.objects.filter(post=self.kwargs['pk'])
Create a PostDetailView to GET a specific post then have the serializer return the comments for that Post.
# serializers.py
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = '__all__'
class PostSerializer(serializers.ModelSerializer):
comments = CommentSerializer(many=True)
class Meta:
model = Post
fields = ('title', 'text', 'comments') # Comments field is recognized by the related_name set in your models.
# views.py
class PostDetailView(generics.RetreiveApiView):
permission_classes = (IsAuthenticated,)
serializer_class = PostSerializer
queryset = Post.objects.all()

Cannot post entry specifying specific id if the id is specified as ForeignKey in Django

I have two models, Book and ReadBy as specified in models.py:
class Book(models.Model):
created = models.DateTimeField(auto_now_add=True)
owner = models.ForeignKey(MyUser)
class ReadBy(models.Model):
created = models.DateTimeField(auto_now_add=True)
owner = models.ForeignKey(MyUser)
book = models.ForeignKey(Book)
views.py:
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
def pre_save(self, obj):
obj.owner = self.request.user
class ReadByViewSet(viewsets.ModelViewSet):
queryset = ReadBy.objects.all()
serializer_class = ReadBySerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
def pre_save(self, obj):
obj.owner = self.request.user
serializers.py:
class BookSerializer(serializers.ModelSerializer):
readby = serializers.SerializerMethodField('get_ready_by')
def get_read_by(self, obj):
user = self.context['request'].user
book = obj
return ReadBy.objects.filter(book=book, owner=user).exists()
class Meta:
model = Book
fields = ('id', 'created', 'owner', 'readby')
class ReadBySerializer(serializers.ModelSerializer):
owner = serializers.Field(source='owner.id')
book = serializers.Field(source='book.id')
class Meta:
model = ReadBy
fields = ('id', 'created', 'owner', 'book')
Book is a ForeignKey in ReadBy. The reason for this is that I want to see if the book has been read by defining the readby field in BookSerializer to true or false.
Now when I try to POST a ReadBy item I cannot explicitly set the book_id, even if I try do I still get the same error:
"Column 'book_id' cannot be null"
What I need to know is how I can explicitly specify book_id to be able to "read" a book.
Thanks.
class ReadBy(models.Model):
... snip ...
book = models.ForeignKey(Book, null=True, blank=True)
OK I solved this.
I forgot to define the book field in the ReadBySerializer as PrimaryKeyRelatedField:
book = serializers.PrimaryKeyRelatedField(source='book.id')
In fact it seems like I can remove this line altogether since PrimaryKeyRelatedField is default if nothing is specified.

Categories