I have a post model that represents a normal post with images and possibly a video. I have a post reply model that represents comments or replies to a post.
Here is the models.py file:
class Category(models.Model):
name = models.CharField(max_length=100, verbose_name="name")
slug = AutoSlugField(populate_from=["name"])
description = models.TextField(max_length=300)
parent = models.ForeignKey(
"self", on_delete=models.CASCADE, blank=True, null=True, related_name="children"
)
created_at = models.DateTimeField(auto_now_add=True, verbose_name="created at")
updated_at = models.DateTimeField(auto_now=True, verbose_name="updated at")
class Meta:
verbose_name = "category"
verbose_name_plural = "categories"
ordering = ["name"]
db_table = "post_categories"
def __str__(self):
return self.name
def get_absolute_url(self):
return self.slug
def video_directory_path(instance, filename):
return "{0}/posts/videos/{1}".format(instance.user.id, filename)
def post_directory_path(instance, filename):
return "posts/{0}/images/{1}".format(instance.post.id, filename)
def reply_directory_path(instance, filename):
return "replies/{0}/images/{1}".format(instance.reply.id, filename)
def reply_videos_directory_path(instance, filename):
return "{0}/posts/{1}/replies/{2}/videos/{3}".format(instance.user.id, instance.post.id, instance.reply.id, filename)
class Post(models.Model):
EV = "Everybody"
FO = "Followers"
FR = "Friends"
AUDIENCE = [
(EV, "Everybody"),
(FO, "Followers"),
(FR, "Friends"),
]
category = models.ForeignKey(Category, on_delete=models.SET_DEFAULT, default=1)
body = models.TextField("content", blank=True, null=True, max_length=5000)
slug = AutoSlugField(populate_from=["category", "created_at"])
video = models.FileField(upload_to=video_directory_path, null=True, blank=True)
can_view = models.CharField(max_length=10, choices=AUDIENCE, default=EV)
can_comment = models.CharField(max_length=10, choices=AUDIENCE, default=EV)
user = models.ForeignKey(
User, on_delete=models.CASCADE, verbose_name="user", related_name="user"
)
published = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = "post"
verbose_name_plural = "posts"
db_table = "posts"
ordering = ["created_at"]
def __str__(self):
return self.body[0:30]
def get_absolute_url(self):
return self.slug
class PostImage(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name="images")
image = models.FileField(
upload_to=post_directory_path, default="posts/default.png", null=True
)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = "post_images"
ordering = ["post"]
def __str__(self):
return self.post
def image_tag(self):
return mark_safe(
'<img src="/storage/%s" width="50" height="50" />' % (self.image)
)
image_tag.short_description = "Image"
class Reply(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
video = models.FileField(
upload_to=reply_videos_directory_path, null=True, blank=True
)
body = models.TextField(max_length=256, default=None)
parent = models.ForeignKey(
"self", on_delete=models.CASCADE, null=True, blank=True, related_name="replies"
)
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Created at")
updated_at = models.DateTimeField(auto_now=True, verbose_name="Updated at")
class Meta:
verbose_name = "post reply"
verbose_name_plural = "post replies"
db_table = "post_reply"
def __str__(self):
return self.reply[0:30]
I have the following serializers to deal with the posts and the replies:
class PostImageSerializer(serializers.ModelSerializer):
class Meta:
model = PostImage
fields = ["id", "image", "post"]
extra_kwargs = {
"post": {"required": True},
}
class PostSerializer(serializers.ModelSerializer):
images = PostImageSerializer(many=True, read_only=True, required=False)
profile = serializers.SerializerMethodField()
class Meta:
model = Post
fields = [
"id",
"can_view",
"can_comment",
"category",
"body",
"images",
"video",
"profile",
"published",
"created_at",
"updated_at",
]
depth = 1
def get_profile(self, obj):
profile_obj = Profile.objects.get(id=obj.user.profile.id)
profile = ShortProfileSerializer(profile_obj)
return profile.data
def create(self, validated_data):
user = User.objects.get(id=self.context['request'].data.get('user'))
category = Category.objects.get(id=self.context['request'].data.get('category'))
new_post = Post.objects.create(**validated_data, category=category, user=user)
images = dict((self.context['request'].FILES).lists()).get('images', None)
if images:
for image in images:
PostImage.objects.create(
image=image, post=new_post
)
return new_post
def update(self, id):
breakpoint()
post = Post.object.all().filter(id=id)
class ReplyImageSerializer(serializers.ModelSerializer):
class Meta:
model = ReplyImage
fields = ["id", "image", "reply"]
extra_kwargs = {
"reply": {"required": True},
}
class ReplySerializer(serializers.ModelSerializer):
images = ReplyImageSerializer(many=True, read_only=True, required=False)
profile = serializers.SerializerMethodField()
post_images = serializers.SerializerMethodField(many=True)
class Meta:
model = Reply
fields = [
"id",
"post",
"post_images",
"video",
"images",
"body",
"parent",
"profile",
"created_at",
"updated_at",
]
depth = 1
def get_profile(self, obj):
profile_obj = Profile.objects.get(id=obj.user.profile.id)
profile = ShortProfileSerializer(profile_obj)
return profile.data
def get_post_images(self, obj):
post_obj = PostImage.objects.get(post=obj.post.id)
post_images = ShortProfileSerializer(post_obj)
return post_images.data
def create(self, validated_data):
new_reply = Reply.objects.create(**validated_data)
images = dict((self.context['request'].FILES).lists()).get('images', None)
if images:
for image in images:
ReplyImage.objects.create(
image=image, reply=new_reply
)
return new_reply
I use the following view to see a specific reply to a post but the related images are missing and I would like to get the related images into the endpoint. I would also like to get the category name and display that instead of the id, but images for now are more important.
I have added a serializer method field to the reply serializer to get the post images into the end point and it gets the images but it gives me an error:
get() returned more than one PostImage -- it returned 4!
Is there something I can do to get multiple objects from a serializer method field, to include in the endpoint or should I use a different approach?
Also how can I get the post category name instead of the ID?
Thank you
CodePudding user response:
Use filter()
method instead of get()
, and pass the resulting QuerySet
object to the serializer class with many=True
class ReplySerializer(serializers.ModelSerializer):
...
post_images = serializers.SerializerMethodField()
class Meta:
model = Reply
fields = [
"...",
"post_images",
]
depth = 1
def get_post_images(self, obj):
images = PostImage.objects.filter(post=obj.post.id)
serializer = PostImageSerializer(images, many=True)
return serializer.data