Assuming im using the default django model, a Post model (code below) and a SavedPost model that links a User to a Post (if the certain user with the certain post exists then that post is saved for that user) and a Follower model that links 2 user (similar to SavedPost).
What im trying to do: An API that for a user, they get all posts for the users they follow, in addition each of these posts has an extra 'field' to say if that post is saved or not.
class Post(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
post_type = models.CharField(max_length=1, choices=[('B', 'Blog'), ('V', 'Video')], default='B')
file_path = models.URLField(null=True)
title = models.CharField(max_length=255)
description = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class SavedPost(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
# A user can save a post only once.
unique_together = ('user', 'post')
class Follower(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="user")
follower = models.ForeignKey(User, on_delete=models.CASCADE, related_name="follower")
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
# A user can follow another user only once
unique_together = ('user', 'follower')
Post serilializer:
class PostSerializer(serializers.ModelSerializer):
"""
Nested serializer for post using SimpleUser and Kingdom.
"""
class Meta:
model = Post
fields = ('id', 'user', 'post_type', 'file_path',
'title', 'description', 'created_at', 'updated_at')
def to_representation(self, instance):
data = super().to_representation(instance)
data['user'] = UserSerializer(
User.objects.get(pk=data['user'])).data
return data
API View:
#permission_classes([IsAuthenticated,])
#api_view(['GET'])
def get_following(request):
user = request.user
following = Follower.objects.filter(follower=user).values('user')
# saved_posts = SavedPost.objects.filter(user=user, post__user__in=following).order_by('-post__created_at')
posts = Post.objects.filter(user__in=following).order_by('-created_at')
serializer = PostSerializer(posts, many=True, context={'request': request})
return JsonResponse(serializer.data, safe=False)
So far with the view I made I can get all the posts that the request.user follows but it doesnt say if they are saved or not. I am looking for say 'is_saved' boolean on post to say if that post is saved for that user or not.
Any help/method to do this appreciated. Thank you.
Use serializers.SerializerMethodField as
class PostSerializer(serializers.ModelSerializer):
is_saved = serializers.SerializerMethodField()
def get_is_saved(self, post_instance):
return SavedPost.objects.filter(user=post_instance.user, post=post_instance).exists()
class Meta:
model = Post
fields = ['id', 'user', 'post_type', 'file_path',
'title', 'description', 'created_at', 'updated_at', 'is_saved']
def to_representation(self, instance):
data = super().to_representation(instance)
data['user'] = UserSerializer(
User.objects.get(pk=data['user'])).data
return data
First of all, just to be clear, I will be defining the related_name option for the ForeignKeys in SavedPost - it's up to you to decide whether to implement this or not:
class SavedPost(models.Model):
user = models.ForeignKey(User, related_name="saved", on_delete=models.CASCADE)
post = models.ForeignKey(Post, related_name="saved", on_delete=models.CASCADE)
...
Now, in your PostSerializer, you could add this field (remember to add it to the fields variable in the Meta inner class - that is if you're using ModelSerializer):
class PostSerializer(serializers.ModelSerializer):
saved = SavedPostSerializer(many=True)
...
To finish it off, define your SavedPostSerializer - above PostSerializer, if in the same file/module:
class SavedPostSerializer(serializers.ModelSerializer):
class Meta:
model = SavedPost
fields = "__all__"
With this, your json should have a nested field with the saved key containing an array of SavedPosts, if there are any related to the Posts retrieved.
Related
Hello everyone, how to limit the number of results using a serializer?
In short, there is a table of comments, which can contain different types of posts.
class CourseComment(models.Model):
user = models.ForeignKey(User, on_delete=models.PROTECT)
content = models.TextField()
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
and here related table
class CourseMessage(models.Model):
course_id = models.ForeignKey(Course, on_delete=models.PROTECT)
author_id = models.ForeignKey(User, on_delete=models.PROTECT)
text = models.TextField() # RAW Format must exclude specials chars before publish
is_pinned = models.BooleanField(default=False)
comments = GenericRelation('CourseComment')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
I made the serializer according to the documentation
https://www.django-rest-framework.org/api-guide/relations/
class CourseMessages(serializers.ModelSerializer):
user = Author(source='authorid', read_only=True)
files = MessageFiles(source='coursemessageattachedfile_set', many=True)
message_comments = MessageComments(source='comments', many=True, read_only=True)
class Meta:
model = CourseMessage
fields = ['text', 'updated_at', 'user', 'files', 'message_comments']
class MessageComments(serializers.RelatedField):
def to_representation(self, value):
ct = ContentType.objects.get_for_model(value)
serializer = Comments(value, read_only=True, source='last_comments')
return serializer.data
class Comments(serializers.ModelSerializer):
author = Author(source='user', read_only=True)
class Meta:
model = CourseComment
fields = ['content', 'author']
Everything works well, but I would like to see first 3 comments.
Maybe someone has encountered such a problem, or can advise how to do it better.
I get this data for the RetrieveAPIView detail page. The first three comments are required for display on the front.
requirements
Django==3.2.5
djangorestframework==3.12.4
Thanks in advance :)
I think you gain nothing by instructing the serializer to return a subset of a resultset. I think the best way is to filter the subset when making the query, or filtering the array after serializing:
result_set = CourseComment.objects.all()[:3] # model
three_first = Comment(result_set, many=True).data # serializer
or
result_set = CourseComment.objects.all() # model
three_first = Comment(result_set, many=True).data[:3] # serializer
Or you can even send a subset of the resultset to the serializer:
result_set = CourseComment.objects.all() # model
three_first = Comment(result_set[:3], many=True).data # serializer
I think what you are looking for is the SerializerMethodField()
So your code should look like this:
class CourseMessages(serializers.ModelSerializer):
user = Author(source='authorid', read_only=True)
files = MessageFiles(source='coursemessageattachedfile_set', many=True)
message_comments = serializers.SerializerMethodField()
def get_message_comments(self, obj):
message_comments = Comments.objects.all()[:3]
return MessageComments(message_comments, source='comments', many=True, read_only=True).data
class Meta:
model = CourseMessage
fields = ['text', 'updated_at', 'user', 'files', 'message_comments']
If you are using a queryset as:
class ViewName(generics.RetriveAPIView):
permission_classes = [IsAuthenticated]
serializer_class = Comments
queryset = CourseComment.objects.all()
then you can use inside your list function:
def list(self, request, *args, **kwargs):
serializer = Comments(self.get_queryset().order_by('-id')[:3], many = True)
Hope it will work for you.
in models.py:
class Post(models.Model):
body = models.TextField(max_length=10000)
date = models.DateTimeField(auto_now_add=True, blank=True)
user = models.ForeignKey(User, on_delete=models.CASCADE)
liked_by = models.ManyToManyField(User, blank=True, related_name='liked_by')
class Meta:
ordering = ['-date']
in serializers.py:
class PostSerializer(serializers.ModelSerializer):
user = UserSerializers()
class Meta:
model = Post
fields = ('body','date','user')
how to count likes of a single post? and also show which user liked the post.
class PostSerializer(serializers.ModelSerializer):
user = UserSerializers()
total_likes = serilaizers.SerializerMethodField()
liked_by = UserSerializers(many=True)
class Meta:
model = Post
fields = ('body','date','user', 'total_likes', 'liked_by')
def get_total_likes(self, instance):
return instance.liked_by.count()
You need to change class Post manyToMany relation adding "through=".
liked_by = models.ManyToManyField(User, through='liked_users', blank=True, related_name='liked_by')
Create manually model LikedUsers:
class LikedUsers(models.Model):
liked_count = models.IntegerField(default=0)
post = models.ForeignKey(Post, related_name='likedusers', on_delete=models.CASCADE)
user = models.ForeignKey(User, related_name='likedusers', on_delete=models.CASCADE)
Get access to likes (post.likedusers[0].liked_count, ... next users)
You can get the total likes of a post using SerializerMethodField.
from rest_framework.fields import SerializerMethodField
class PostSerializer(serializers.ModelSerializer):
user = UserSerializers()
total_likes = SerializerMethodField()
class Meta:
model = Post
fields = ('body','date','user')
def get_total_likes(self, instance):
return instance.liked_by.all().count()
Your liked_by field is just referencing to User model, but not to User model and you have only who liked your post but likes quantity. So you can query certain post and count User quantity, but I suggest to have separate filed of likes count
I have a page where a blog post detail is displayed. Under the post, there is a section where user can comment after inputing thier name subject and text in a comment box. Now i have to make an api for this. I want to make a post api such that that comment is stored/associated to that particular blogpost detail. That means i need blogpost id to pass while posting comment. How to do that??
class BlogPost(models.Model):
CATEGORY_CHOICES = (
('travel_news', 'Travel News',),
('travel_tips', 'Travel Tips',),
('things_to_do', 'Things to Do',),
('places_to_go', 'Places to Go'),
)
image = models.ImageField(blank=True, null=True)
categories = models.CharField(max_length=64, choices=CATEGORY_CHOICES, default='travel_news')
description = models.CharField(max_length=255)
content = RichTextUploadingField()
# todo support for tags
tags = models.CharField(max_length=255, default='#travel') #todo
date_created = models.DateField()
#property
def html_stripped(self):
from django.utils.html import strip_tags
return strip_tags(self.content)
#property
def comments(self):
return self.comments_set.all()
Here are my serializers:
class CommentPostSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
# fields = '__all__'
fields = ['name', 'email', 'subject', 'comment',]
class BlogPostSerializer(serializers.ModelSerializer):
comments = CommentListSerializer(many=True)
class Meta:
model = BlogPost
fields = ['image', 'categories', 'description', 'content', 'tags', 'date_created', 'comments']
# fields = '__all__'
Here is my view:
class CommentCreateAPIView(CreateAPIView):
queryset = Comment.objects.all()
serializer_class = CommentPostSerializer
Here are my models :
class Profile(models.Model):
user = models.ForeignKey(User, related_name="profile", on_delete=PROTECT)
plan = models.ForeignKey(Plans, on_delete=PROTECT)
full_name = models.CharField(max_length=2000)
company_name = models.CharField(max_length=50, null=True, blank=True)
activation_token = models.UUIDField(default=uuid.uuid4)
activated = models.BooleanField(default=False)
thumb = models.ImageField(upload_to='uploads/thumb/', null=True, blank=True)
renew_data = models.DateField()
is_paid = models.BooleanField(default=False)
And as you see the Profile model have user field that is related to the Abstract user of django framework. now here is how i call them using an API :
Serializers
class ProfileSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Profile
fields = ['company_name']
class UserSerializer(serializers.HyperlinkedModelSerializer):
profile_set = ProfileSerializer(
read_only=True, many=True) # many=True is required
class Meta:
model = User
depth = 1
fields = ['username', 'id', 'profile_set']
But when I call the API it shows only the fields username and 'id but not the profile_set
Your UserSerializer should like this,
class UserSerializer(serializers.HyperlinkedModelSerializer):
# no need to set `profile.all` as you have related name profile defined in your model
profile_set = ProfileSerializer(source='profile', many=True)
class Meta:
model = User
depth = 1
fields = ['username', 'id', 'profile_set']
OR,
class UserSerializer(serializers.HyperlinkedModelSerializer):
profile = ProfileSerializer(many=True) # as you have related name `profile`
class Meta:
model = User
depth = 1
fields = ['username', 'id', 'profile']
Try setting the source of your serializer:
profile_set = ProfileSerializer(
source='profile.all',
read_only=True, many=True
)
It looks like you've set the related_name on your foreign key:
user = models.ForeignKey(User, related_name="profile", on_delete=PROTECT)
This defines the reverse relation name, so that's how you need to refer to it in DRF, too:
class UserSerializer(serializers.HyperlinkedModelSerializer):
profile = ProfileSerializer(read_only=True, many=True)
class Meta:
model = User
depth = 1
fields = ['username', 'id', 'profile']
Since it's clearly a plural, I'd also suggest you rename profile to profiles.
I would like to be able to send an AJAX POST request to my API endpoint to create a new instance of my Asset model with multiple Category instances referenced in my Asset model, hence the many-to-many field type in my Asset model.
I'm able to successfully POST and create new Asset instances, however my category field won't accept any data at all. The category field remains empty when a new Asset instance is created. I think it has something to do with my CategorySerializer. I'm still learning how to use Django REST Framework so I'd appreciate if I could get some help figuring out how to work with serializers in Django REST Framework.
I've already tried modifying the AssetSerializer create method to handle parsing the JSON and validating the data but that hasn't worked. I've also tried other solutions suggested in other posts I've found on StackOverflow but haven't found anything that works for my situation.
Here's my serializers.py file:
class CategorySerializer(serializers.ModelSerializer):
name = serializers.CharField(required=False, read_only=True)
class Meta:
model = Category
fields = ('id', 'name')
class AssetSerializer(serializers.ModelSerializer):
name = serializers.CharField(allow_null=True)
description = serializers.CharField(allow_null=True)
manufacturer = serializers.CharField(allow_null=True)
uid = serializers.UUIDField(read_only=True, allow_null=True)
borrower = BorrowerSerializer(allow_null=True, read_only=True)
condition = serializers.ChoiceField(choices=Asset.CONDITION_TYPE, default='g', allow_null=True)
owner = serializers.ReadOnlyField(source='owner.username')
return_date = serializers.DateField(allow_null=True)
checked_out = serializers.BooleanField(allow_null=True)
category = CategorySerializer(required=False, many=True)
class Meta:
model = Asset
fields = ('uid',
'name',
'manufacturer',
'model',
'description',
'owner',
'condition',
'category',
'borrower',
'checked_out',
'return_date',
'is_dueback',
)
def update(self, instance, validated_data):
instance.borrower = validated_data.get('borrower', instance.borrower)
instance.return_date = validated_data.get('return_date', instance.return_date)
instance.checked_out = validated_data.get('checked_out', instance.checked_out)
instance.name = validated_data.get('name', instance.name)
instance.manufacturer = validated_data.get('manufacturer', instance.manufacturer)
instance.model = validated_data.get('model', instance.model)
instance.description = validated_data.get('description', instance.description)
instance.condition = validated_data.get('condition', instance.condition)
instance.category = validated_data.get('category', instance.category)
instance.save()
return instance
def create(self, validated_data):
return Asset.objects.create(**validated_data)
Here's my Asset model:
class Asset(models.Model):
"""Model representing an Asset"""
uid = models.UUIDField(primary_key=True, default=uuid.uuid4)
name = models.CharField(max_length=200)
manufacturer = models.CharField(max_length=64)
model = models.CharField(max_length=128)
description = models.TextField()
category = models.ManyToManyField(Category)
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
borrower = models.ForeignKey(Borrower, on_delete=models.CASCADE, null=True, blank=True)
checked_out = models.BooleanField(default=False)
return_date = models.DateField(null=True, blank=True)
CONDITION_TYPE = (
('e', 'Excellent'),
('g', 'Good'),
('f', 'Fair'),
('p', 'Poor'),
)
condition = models.CharField(
max_length=1,
choices=CONDITION_TYPE,
blank=True,
help_text='Asset condition')
class Meta:
ordering = ['return_date']
#property
def is_dueback(self):
if self.return_date and date.today() > self.return_date:
return True
return False
def display_category(self):
"""Create a string for the Category. This is required to display category in Admin."""
return ', '.join(category.name for category in self.category.all())
display_category.short_description = 'Category'
def __str__(self):
return f'{self.uid} - {self.name}'
def get_absolute_url(self):
return reverse('asset-detail', args=[str(self.uid)])
Here's my Category model:
class Category(models.Model):
"""Model representing an Asset category"""
name = models.CharField(max_length=128)
def __str__(self):
return self.name
I'd appreciate any help you could provide. Thank you in advance.
i'm almost new in DRF but i try to help. why you writing all the field in serializer when you using ModelsSerializer? not need to telling ModelSerializer what type of field should be because you are pointing to model in class Meta and DRF know about fields and type and etc . second about allow_null=True in serializer, when Model haven't null=True you can't except DRF can create a not null-able field for instance with null=True so if you wnt a field can be null just add null=True in Model class . for your problem about ManytoMantry field try to use Primary key relation for ManyToMany fields in your serializers then pass id of Category instances in list:
class AssetSerializer(serializers.ModelSerializer):
borrower = BorrowerSerializer(allow_null=True, read_only=True)
category = serializers.PrimaryKeyRelatedField(many=True, queryset=Category.objects.all())
class Meta:
model = Asset
fields = ('uid',
'name',
'manufacturer',
'model',
'description',
'owner',
'condition',
'category',
'borrower',
'checked_out',
'return_date',
'is_dueback',
)
read_only_fields = ( 'uid' , ) # this fields will be read_only
depending on how you using this serializer in your view for save and update have difference way. if your view is generics class so will do create and update itself by POST and PUT method .and for other class view that isn't belong to generics DRF view you can using serializer.save() to create a new instance.wish help you.
pass data something like:
{
"name" : "foo",
"manufacture" : "foo",
.
.
.
"category" : [1,2,3,24,65]
}