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)
Related
I'm looking to retrieve all employees (User) of the current User (id is given). Employees is a ManyToMany field of User. Currently my query retrieves the current user. And user.employees just returns the ids of all employees.
Would it be possible to make a query to retrieve all the Employees of the current User right away? Or am I just supposed to send more API calls (from the front end) for every user where I retrieve the data of the users by id?
Would be awesome if someone could steer me in the right direction. :)
views.py
# display all your employees
class EmployeeViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
def list(self, request):
queryset = User.objects.filter(pk=request.user.pk) #get current user
serializer = UserSerializer(queryset, many=True)
return Response(serializer.data)
models.py
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
employees = models.ManyToManyField(User, related_name='employees')
serializers.py
class UserSerializer(UserDetailsSerializer):
class Meta(UserDetailsSerializer.Meta):
fields = UserDetailsSerializer.Meta.fields + ('employees')
def update(self, instance, validated_data):
profile_data = validated_data.pop('userprofile', {})
employees = profile_data.get('employees')
instance = super(UserSerializer, self).update(instance, validated_data)
# get and update user profile
profile = instance.userprofile
if profile_data:
if employees:
profile.employees = employees
profile.save()
return instance
rest_auth/serializers.py (dependency)
class UserDetailsSerializer(serializers.ModelSerializer):
"""
User model w/o password
"""
class Meta:
model = UserModel
fields = ('pk', 'username', 'email', 'first_name', 'last_name')
read_only_fields = ('email', )
Example user
class EmployeeViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
def list(self, request):
queryset = self.queryset.filter(id__in=request.user.employees.all())
serializer = UserSerializer(queryset, many=True)
return Response(serializer.data)
Also, in the serializer you may have to do the following to get the desired response structure.
class UserSerializer(UserDetailsSerializer):
employees = serializers.SerializerMethodField()
class Meta(UserDetailsSerializer.Meta):
fields = UserDetailsSerializer.Meta.fields + ('employees')
def get_employees(self, obj):
return obj.userprofile.employees.all()
You can use a filter like:
request.user.userprofile.employees.all() to fetch the employees for the specific user.
class EmployeeViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
def list(self, request):
queryset = request.user.userprofile.employees.all() #get all employees for current user
serializer = UserSerializer(queryset, many=True)
return Response(serializer.data)
This documention about One-to-one relationships would also be useful.
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',)
I have a user model with many to many relationships with the model team:
class User(AbstractBaseUser):
"""
model to store user infomation
"""
.....
team = models.ManyToManyField(
"team.Team", blank=True,
related_name="team_member")
.....
Here is a snippet of UserSerializer:
class UserSerializer(serializers.ModelSerializer):
"""
User serializer for user ModelViewSet
"""
.....
team = serializers.PrimaryKeyRelatedField(
queryset=Team.objects.all(), many=True,
required=False,
allow_null=True,
)
.....
class Meta:
model = User
exclude = ()
and here is the view for same:
class UserViewSet(viewsets.ModelViewSet):
""" User model view """
.....
def create(self, request, *args, **kwargs):
request.data._mutable = True
team = request.data.get('team')
team = json.loads(team)
request.data['team'] = team
serializer = self.get_serializer_class()
serializer = serializer(data=request.data)
if serializer.is_valid(raise_exception=True):
self.perform_create(serializer)
.....
Below is attached screenshots of request body using postman form-data:
Anyone can guide me on what I am doing wrong here.
Please try with getlist() method instead of get() method as like:
team = request.data.getlist('team[]')
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()
I have models which consist in a User model and a Present one. A User can have multiple Present but a Present has a unique User:
My models.py is:
from django.db import models
from django.contrib.auth.models import User
class Present(models.Model):
name = models.CharField(max_length=15)
price = models.FloatField()
link = models.CharField(max_length=15)
isAlreadyBought = models.BooleanField()
user = models.ForeignKey(User, related_name='presents', on_delete=models.CASCADE)
My serializers.py are:
from django.contrib.auth.models import User, Group
from rest_framework import serializers
from blog.models import Present, Location
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
presents = serializers.PrimaryKeyRelatedField(many=True, queryset=Present.objects.all(), required=False)
class Meta:
model = User
fields = ('username', 'password', 'email', 'presents')
def create(self, validated_data):
user = super().create(validated_data)
if 'password' in validated_data:
user.set_password(validated_data['password'])
user.save()
return user
class PresentSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), read_only=False, many=False)
class Meta:
model = Present
fields = ('name', 'link', 'price', 'isAlreadyBought', 'user')
def create(self, validated_data):
return Present.objects.create(**validated_data)
Currently, if I want to get the all the presents associate with a given User, I use the primary key (in views.py):
class PresentsOfUser(viewsets.ModelViewSet):
queryset = Present.objects.all().filter(user=33)
serializer_class = PresentSerializer
However, I would rather use the username field of the User instead of the primary key.
I have tried using a SlugRelatedField but I am not sure this is the right way to achieve my goal:
class PresentSerializer(serializers.ModelSerializer):
user = serializers.SlugRelatedField(queryset=User.objects.all(), slug_field='username', read_only=False, many=False)
class Meta:
model = Present
fields = ('name', 'link', 'price', 'isAlreadyBought', 'user')
def create(self, validated_data):
return Present.objects.create(**validated_data)
And with this modification, I now use the following View to get the user 'Marcel' whose id is 33:
class PresentsOfUser(viewsets.ModelViewSet):
queryset = Present.objects.all().filter(user='Marcel')
serializer_class = PresentSerializer
But in this case, I get:
ValueError: invalid literal for int() with base 10: 'Marcel'
But if I replace user='Marcel' by user=33 (as previously), I get:
[{"name":"Nintendo","link":"fake_link","price":50.8,"isAlreadyBought":true,"user":"Marcel"},{"name":"Gamecube","link":"fake_link","price":50.8,"isAlreadyBought":true,"user":"Marcel"}]
where the user field is now the username and not the user's id.
However, I do not understand why filtering with user='Marcel' fails...
I ended up by overriding the get_querysetmethod while keeping the PrimaryKeyRelatedField in my serializer (with user__username='Marcel'as mishbah suggested):
class PresentsOfUser(viewsets.ModelViewSet):
serializer_class = PresentSerializer
def get_queryset(self):
"""
Optionally restricts the returned purchases to a given user,
by filtering against a `username` query parameter in the URL.
"""
queryset = Present.objects.all()
username = self.kwargs['user']
if username is not None:
queryset = queryset.filter(user__username=username)
if len(queryset) == 0:
raise Http404
return queryset
It also works when replacing PrimaryKeyRelatedField by SlugRelatedField:
user = serializers.SlugRelatedField(queryset=User.objects.all(), slug_field='username', read_only=False, many=False)
and adding to_field='username'in the ForeignKey of my Present model:
user = models.ForeignKey(User, related_name='presents', to_field='username', on_delete=models.CASCADE)
I think your issue is w/ this line:
queryset = Present.objects.all().filter(user='Marcel')
With the assumption Marcel is username with pk => 33
You can't filter using a string, instead something like this:
queryset = Present.objects.all().filter(user__username='Marcel')
Hope that helps.