I've set the default post author to be null, and used the form_valid function to override the post author and assign to it the current logged in user.
the form_valid() function is taken from the official django docs but for some reason its doesnt do anything.
my django versions are:
django-rest-framework = 3.12.2.
django = 3.1.4
models.py
class Recipe(models.Model):
id = models.UUIDField(
primary_key=True,
default=uuid.uuid4,
editable=False
)
author = models.ForeignKey(get_user_model() , on_delete=models.CASCADE, null=True) #
title = models.CharField(max_length=150)
description = models.TextField(blank=True)
serializers.py
class RecipeCreateSerializer(serializers.ModelSerializer):
class Meta:
model = Recipe
fields = ('title', 'description')
views.py
class RecipeCreate(CreateAPIView):
permission_classes = (permissions.IsAuthenticated, )
queryset = Recipe.objects.all()
serializer_class = RecipeCreateSerializer
def form_valid(self, form):
form.instance.author = self.request.user
return super(RecipeCreate, self).form_valid(form)
hopefully someone out here will know how to fix this.
thanks in advance
I think you are mixing up Django's class based views and Django rest framework views. In the docs, it is stated that if you want to use request.user in your serializer, you must use the perform_create method. So first, you would have to add the author field to your RecipeCreateSerializer in serializers.py.
class RecipeCreateSerializer(serializers.ModelSerializer):
class Meta:
model = Recipe
fields = ('title', 'description', 'author') # Add author now
and your RecipeCreate View would now have the perform_create method instead of form_valid:
class RecipeCreate(CreateAPIView):
permission_classes = (permissions.IsAuthenticated, )
queryset = Recipe.objects.all()
serializer_class = RecipeCreateSerializer
def perform_create(self, serializer):
serializer.save(user=self.request.user)
Related
I am very new to Django Rest Framework (DRF). I have read that viewsets are very good, because it reduces the amount of code, but I found it more complex.
Description:
Imagine that I want to implement a phonebook API, in which we have some users and each of them has it own contacts and each contact can have several phone number. So, I have three models here.
User (Default Django model)
Contact
class Contact(models.Model):
owner = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='contacts'
)
name = models.CharField(
max_length=70
)
description = models.TextField()
Phone Number
class Phones(models.Model):
person = models.ForeignKey(
Contact,
related_name="phones",
on_delete=models.CASCADE,
)
phone_no = models.CharField(
max_length=11,
)
Problem Definition
What I want is to create new contact with the current request.user. So I should have my contact.serializers like the following:
class ContactSerializer(serializers.ModelSerializer):
owner = serializers.SlugRelatedField(queryset=User.objects.all(), slug_field='user')
class Meta:
model = Contact
fields = ['id', 'owner', 'name', 'description']
read_only_fields = ['owner']
and my views is like:
class ContactViewSet(viewsets.ModelViewSet):
queryset = Contact.objects.all()
serializer_class = ContactSerializer
permission_classes = [IsCreator]
def get_permissions(self):
if self.request.method == "GET":
self.permission_classes = [IsCreator, permissions.IsAdminUser,]
if self.request.method == "POST":
self.permission_classes = [permissions.IsAuthenticated,]
if self.request.method == "PUT":
self.permission_classes = [IsCreator]
if self.request.method == "DELETE":
self.permission_classes = []
return super(ContactViewSet, self).get_permissions()
Error Whenever I want to post a new contact using postman, I have pass the name, description and owner and it should automatically detects the owner from the request but it doesn't and I have got the following error:
PS: If it is necessary to checkout the project here is my project link.
What should I do?
The problem was solved after applying the below changes.
apps.contacts.views.py:
add the following method to the body of the class
def perform_create(self, serializer):
return serializer.save(owner = self.request.user)
apps.contacts.serializers.py:
class ContactSerializer(serializers.ModelSerializer):
class Meta:
model = Contact
fields = ['id', 'name', 'description', 'owner']
PS I am also looking for more answers, in this case add your answer.
One thing you could do is add currentuserdefault. like this
class ContactSerializer(serializers.ModelSerializer):
owner = serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model = Contact
fields = ['id', 'owner', 'name', 'description']
And in your view code remember to pass request in context. Like this
class ContactViewSet(viewsets.ModelViewSet):
queryset = Contact.objects.all()
serializer_class = ContactSerializer
permission_classes = [IsCreator]
def get_serializer_context(self):
context = super().get_serializer_context()
context.update({"request": self.request})
return context
I am setting up a Django REST application where peopple can review restaurants. So far I have those models:
class RestaurantId(models.Model):
maps_id = models.CharField(max_length=140, unique=True)
adress = models.CharField(max_length=240)
name = models.CharField(max_length=140)
class RestaurantReview(models.Model):
review_author = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)
restaurant_id = models.ForeignKey(RestaurantId, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class StarterPics(models.Model):
restaurant_review_id = models.OneToOneField(RestaurantReview,
on_delete=models.CASCADE)
pics_author = models.ForeignKey(User, on_delete=models.CASCADE)
restaurant_id = models.ForeignKey(RestaurantId, on_delete=models.CASCADE)
name_1 = models.CharField(max_length=40)
picture_1 = models.ImageField()
My serializers:
class RestaurantIdSerializer(serializers.ModelSerializer):
class Meta:
model = RestaurantId
field = fields = '__all__'
class RestaurantReviewSerializer(serializers.ModelSerializer):
class Meta:
model = RestaurantReview
field = fields = '__all__'
class StarterPicsSerializer(serializers.ModelSerializer):
class Meta:
model = StarterPics
fields = '__all__'
def validate_restaurant_review_id(self, value)
if value.review_author != self.request.user:
raise serializers.ValidationError("User has not reviewed the restaurant")
return value
My views:
class RestaurantIdViewset(viewsets.ModelViewSet):
queryset = models.RestaurantId.objects.all()
serializer_class = serializers.RestaurantIdSerializer
class RestaurantReviewViewset(viewsets.ModelViewSet):
queryset = models.RestaurantReview.objects.all()
serializer_class = serializers.RestaurantReviewSerializer
permission_classes = [IsAuthenticatedOrReadOnly,IsAuthorOrReadOnly]
def perform_create(self, serializer):
serializer.save(review_author=self.request.user)
class StarterPicsViewset(viewsets.ModelViewSet):
queryset = models.StarterPics.objects.all()
serializer_class = serializers.StarterPicsSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
I have set up permissions as well so only the review_author can update his reviews and pics_author can update his pictures.
My permissions:
class IsOwnReviewOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.pics_author == request.user
class IsAuthorOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.review_author == request.user
When running Django server I got a 'StarterPicsSerializer' object has no attribute 'request'
This validation is for user that have not written the review (review_author) can't POST pictures in StarterPics. So only the User that creates the review can post pictures on it.
I've tried another validation with no luck either:
def validate_restaurant_review_id(self, value):
if not RestaurantReview.objects.filter(restaurant_review_id=value,
review_author=self.request.user).exists():
raise serializers.ValidationError('Not your review')
return value
You could provide extra context to the serializer in addition to the object being serialized by passing a context argument when instantiating the serializer in your view.
serializer = RandomSerializer(instance, context={'request': request})
If you use Generic Views or ModelViewSet(inherited form GenericAPIView), then request is already available in your serializer self.context dict
class StarterPicsSerializer(serializers.ModelSerializer):
class Meta:
model = StarterPics
fields = '__all__'
def validate_restaurant_review_id(self, value):
print(self.context['request'])
It is my first project using Django rest framework and i'm struggling to get this right. I know that in mainstream Django framework, if i need to add extra contexts to a class-based view, i will do something like this:
class PostDetail(DetailView):
model = Post
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Add in a QuerySet of all the books by a certain author John
context['john_books'] = Book.objects.filter(author=john)
return context
then, i will be able to access the context 'john_books' in my template. Now i need to do same by passing extra contexts to my PostViewSets. On the detail view, i want to access the list of post authored by that post author in my api endpoint (something like 'Posts from the same author'). I have read about get_serializer_context but still can't figure out how to implement it. This is what i have so far:
class PostViewSets(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
def get_serializer_context(self):
context = super(PostViewSets, self).get_serializer_context()
author = self.get_object.author
author_posts = self.get_queryset().filter(author=author)
context.update({'author_posts': author_posts})
return context
i get this error:
AttributeError at /posts/ 'function' object has no attribute 'author'
My Post Model:
class Post(models.Model):
title = models.CharField(max_length=100, unique=True)
body = models.TextField()
is_featured = models.BooleanField(default=True)
viewcount = models.IntegerField(default=0)
author = models.ForeignKey(User, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
and my PostSerializer class:
class PostSerializer(serializers.ModelSerializer):
author = UserSerializer()
class Meta:
model = Post
fields = ['id', 'title', 'body', 'author', 'viewcount', 'is_featured', 'created']
You have to use get_object as func, not as property:
class PostViewSets(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
def get_serializer_context(self):
context = super(PostViewSets, self).get_serializer_context()
author = self.get_object().author
author_posts = self.get_queryset().filter(author=author)
context.update({'author_posts': author_posts})
return context
1. The Error Message
AttributeError at /posts/ 'function' object has no attribute 'author'
explicitly explains what and where is the problem:
author = self.get_object.author
I guess you tried to do something like this
author = self.get_object().author
2. A DRF ViewSet
responses with data serialized by corresponding Serializer. So you don't need to change the ViewSet, but update the Serializer with something like:
class PostListSerializer(serializers.ModelSerializer):
... some fields ...
class Meta:
model = Post
fields = [ ... some fields ... ]
class PostDetailsSerializer(serializers.ModelSerializer):
... some fields ...
author_posts = PostListSerializer(source="author.post_set", many=True, read_only=True)
class Meta:
model = Post
fields = [ ... some fields ... , 'author_posts']
or with SerializerMethodField
class PostDetailsSerializer(serializers.ModelSerializer):
... some fields ...
author_posts = serializers.SerializerMethodField()
def get_author_posts(self, obj):
return PostListSerializer(instance=obj.post_set.all(), many=True).data
class Meta:
model = Post
fields = [ ... some fields ... , 'author_posts']
I didn't try this exact code, but this is the main idea.
you can pass some context into serializers this way,
serializer = self.serializer_class(instance=self.get_object(),
context={'request': request}
)
I am trying to get only courses belonging to a particular user below I have the model, serializer and view I am using to try and achieve this. If I delete the entire get_queryset function from the view the api returns the appropriate user and every course created by every user. If get_queryset remains, the api always returns user not found and still gives every course that exists. Can anyone point me to how I can achieve my desired result.
view:
class UserDetail(generics.RetrieveUpdateDestroyAPIView):
permission_classes = [IsProfileOwnerOrReadOnly]
# queryset = User.objects.all()
serializer_class = UserSerializer
def get_queryset(self):
user = self.request.user
queryset = User.objects.all()
if user is not None:
queryset = queryset.filter(courses__owner_id=user.id)
return queryset
serializer
class UserSerializer(serializers.ModelSerializer):
courses = serializers.PrimaryKeyRelatedField(
many=True, queryset=Course.objects.all())
class Meta:
model = User
fields = ['id', 'username', 'courses']
Model
class Course (models.Model):
title = models.CharField(max_length=200)
description = models.TextField()
pub_date = models.DateField(default=date.today)
owner = models.ForeignKey('auth.User', related_name='courses', on_delete=models.CASCADE)
def __str__(self):
return self.title
You need to filter objects by user
class CreatePostsView(viewsets.ModelViewSet):
model = Post
serializer_class = PostsSerializer
def get_queryset(self):
user = self.request.user
return Post.objects.filter(owner=user)
class CoursesByOwnerView(RetrieveModelMixin, GenericViewSet):
serializer_class = YourModelSerializer
authentication_classes =[TokenAuthentication,]
permission_classes = [IsAuthenticated,]
def list(self, request, *args, **kwargs):
course_taker = self.request.user
courses = YourModel.objects.filter(owner=course_taker).values('your_model_fields')
return Response(courses)
Given your answer in the comments:
Either you use self.request.user given by the authentication middleware. In this case, it will only work for authenticated users, and you can't see courses for another User.
Either you use the endpoint users/<int:pk>/ you mentioned. In this case, you can fetch the user with:
class UserDetail(generics.RetrieveUpdateDestroyAPIView):
permission_classes = [IsProfileOwnerOrReadOnly]
serializer_class = UserSerializer
def get_queryset(self):
return UserDetail.objects.filter(pk=self.kwargs["pk"])
See this thread if you need another example: Django 2.0 url parameters in get_queryset
EDIT: In both cases, change your UserSerializer with:
class UserSerializer(serializers.ModelSerializer):
courses = serializers.PrimaryKeyRelatedField(
many=True, read_only=True)
class Meta:
model = User
fields = ['id', 'username', 'courses']
I use below solution to check is the user viewed the post or not.
Best way to make "viewed" attribute for messages inside user group?
and in django-rest-framework, i create a ListApiView to get all posts:
class PostListView(ListAPIView):
serializer_class = PostSerializer
permission_classes = (IsAuthenticated, )
pagination_class = PostListPagination
def get_queryset(self):
return Post.objects.filter(state='published').order_by('-created')
and the serializers:
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields= '__all__'
now i want a boolean field named "viewed" for each post in PostListView to show that is the authenticated user viewed this post or not.
something like this:
class PostSerializer(serializers.ModelSerializer):
viewed = serializers.BooleanField(read_only=True)
class Meta:
model = Post
fields= '__all__'
def check_is_viewed(current_user, post_instance):
# if user viewed this post:
viewed.value = True
# else:
viewed.value = False
You could use MethodField.
class PostSerializer(serializers.ModelSerializer):
viewed = serializers.SerializerMethodField()
class Meta:
model = Post
fields= '__all__'
def get_viewed(self, obj):
return obj.viewers.exist()