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()
Related
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)
I created a serializer which the "user" below is from another Serializer which i imported, now the imported serializer(PubliceProfileSerializer) works fine on its own but it does not display the content of USER when i call it my browser from this serializer. Every other item shows except the user. Please help
from rest_framework import serializers
from users.api.serializers import PublicProfileSerializer
from blog.models import Post
class PostSerializer(serializers.ModelSerializer):
user = PublicProfileSerializer(source='users.profile', read_only=True)
category = serializers.SerializerMethodField()
label = serializers.SerializerMethodField()
class Meta:
model = Post
fields = '__all__'
def get_category(self, obj):
return obj.get_category_display()
def get_label(self, obj):
return obj.get_label_display()
Add you your serializer the list of fields. Replace
fields = '__all__'
with
fields = ('id', 'user', 'category', 'label')
because:
fields = '__all__'
will only populate id, category and label from the Post model, but will not provide the nested user serializer, so it becomes:
class Meta:
model = Post
fields = ('id', 'user', 'category', 'label')
or
class PostSerializer(serializers.ModelSerializer):
user = PublicProfileSerializer(many=False,
source='users.profile',
read_only=True)
category = serializers.SerializerMethodField()
label = serializers.SerializerMethodField()
class Meta:
model = Post
fields = ('id', 'user', 'category', 'label')
def get_category(self, obj):
return obj.get_category_display()
def get_label(self, obj):
return obj.get_label_display()
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 am using django rest framework, and I have an object being created via a modelviewset, and a modelserializer. This view is only accessible by authenticated users, and the object should set its 'uploaded_by' field, to be that user.
I've read the docs, and come to the conclusion that this should work
viewset:
class FooViewset(viewsets.ModelViewSet):
permission_classes = [permissions.IsAdminUser]
queryset = Foo.objects.all()
serializer_class = FooSerializer
def get_serializer_context(self):
return {"request": self.request}
serializer:
class FooSerializer(serializers.ModelSerializer):
uploaded_by = serializers.PrimaryKeyRelatedField(
read_only=True, default=serializers.CurrentUserDefault()
)
class Meta:
model = Foo
fields = "__all__"
However, this results in the following error:
django.db.utils.IntegrityError: NOT NULL constraint failed: bar_foo.uploaded_by_id
Which suggests that "uploaded_by" is not being filled by the serializer.
Based on my understanding of the docs, this should have added the field to the validated data from the serializer, as part of the create method.
Clearly I've misunderstood something!
The problem lies in the read_only attribute on your uploaded_by field:
Read-only fields are included in the API output, but should not be
included in the input during create or update operations. Any
'read_only' fields that are incorrectly included in the serializer
input will be ignored.
Set this to True to ensure that the field is used when serializing a
representation, but is not used when creating or updating an instance
during deserialization.
Source
Basically it's used for showing representation of an object, but is excluded in any update and create-process.
Instead, you can override the create function to store the desired user by manually assigning it.
class FooSerializer(serializers.ModelSerializer):
uploaded_by = serializers.PrimaryKeyRelatedField(read_only=True)
def create(self, validated_data):
foo = Foo.objects.create(
uploaded_by=self.context['request'].user,
**validated_data
)
return foo
DRF tutorial recommend to override perform_create method in this case and then edit serializer so, that it reflect to new field
from rest_framework import generics, serializers
from .models import Post
class PostSerializer(serializers.HyperlinkedModelSerializer):
author = serializers.ReadOnlyField(source='author.username')
class Meta:
model = models.Post
fields = ['title', 'content', 'author']
class ListPost(generics.ListCreateAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
def perform_create(self, serializer):
return serializer.save(author=self.request.user)
Cleaner way:
class PostCreateAPIView(CreateAPIView, GenericAPIView):
queryset = Post.objects.all()
serializer_class = PostCreationSerializer
def perform_create(self, serializer):
return serializer.save(author=self.request.user)
class PostCreationSerializer(serializers.ModelSerializer):
author = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Post
fields = ("content", "author")