0

I have a Post model:

class Post(models.Model):
profile = models.ForeignKey(Profile, related_name="posts", on_delete=CASCADE)
project = models.ForeignKey(Project, related_name="posts", on_delete=CASCADE)
title = models.CharField(verbose_name="Title", max_length=150)
body = models.TextField(verbose_name="Content", max_length=1300)
image_url = models.URLField(max_length=256, null=True, blank=True)
liked_by = models.ManyToManyField(Profile, related_name="post_likes")
date_published = models.DateTimeField(auto_now_add=True, verbose_name="Date published")
date_modified = models.DateTimeField(auto_now=True, verbose_name="Date modified")
is_active = models.BooleanField(verbose_name="Active", default=True)
tags = TaggableManager(blank=True, related_name="posts")

objects = models.Manager()

It has a "liked_by" M2M relations to track profiles liked a post.

The goal is to get a Post object with given ID and attach a data of profiles liked this post (id, username, photo).

For this moment my query is :

        post = Post.objects.filter(pk=post_pk)\
                       .select_related("profile", "project") \
                       .prefetch_related("liked_by") \
                       .annotate(comments_count=Count("comments"),
                                 likes_count=Count("liked_by"),
                                 users_liked=ArrayAgg("liked_by"))\
                       .first()

Later it packed to DTO (Dataclass), serializes and responses for API call.

DTO Class:

@dataclass(frozen=True)
class PostDetailDTO:
    id: str | int
    profile_id: str | int
    project_id: str | int
    title: str
    body: str
    image_url: str
    tags: Set[str]
    date_published: datetime | None
    likes_count: int
    comments_count: int
    users_liked: List[dict] | None = None
    is_active: bool = True

Serializer:

class PostDetailDTOSerializer(serializers.Serializer):
    id = serializers.IntegerField(required=False, read_only=True)
    profile_id = serializers.IntegerField(read_only=True)
    project_id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(max_length=150, read_only=True)
    body = serializers.CharField(max_length=1300, read_only=True)
    image_url = serializers.URLField(read_only=True)
    tags = serializers.ListSerializer(child=serializers.CharField(), read_only=True)
    date_published = serializers.DateTimeField(read_only=True)
    users_liked = serializers.ListSerializer(child=serializers.CharField(), read_only=True)
    likes_count = serializers.IntegerField(read_only=True)
    comments_count = serializers.IntegerField(read_only=True)
    is_active = serializers.BooleanField(read_only=True)

Example of API response for this query:

{
    "post": {
        "id": 25,
        "profile_id": 2,
        "project_id": 1,
        "title": "STATUS  TEST",
        "body": "testing filtering",
        "image_url": null,
        "tags": [],
        "date_published": "2023-07-06T12:44:37.134234Z",
        "users_liked": [
            "2",
            "4"
        ],
        "likes_count": 2,
        "comments_count": 0,
        "is_active": false
    }
}

The question is : how to make it display profile data in format [{"id": 10, "username": "hello_world", "photo": "www.url.com}, {"id":20, "username": "my_username", "photo": "www.another.com"}] instead of just ID? Probably there is a way through dataclass modifications by using "for in" through "liked_by" field but I'm looking if there is more easier way to perform it within Database/ORM. Database in use - PostgreSQL.

UPDATE: Updated with ProfileDTO and Serializer. ProfileDTO: @dataclass(frozen=True) class ProfileDTO: id: int | None first_name: str last_name: str username: str linkedin_url: str about: str photo: str rating: float cv_file: str # subscripted_projects: ProjectDTO role_id: int specialization_id: int tools: list[ToolDTO]

Profile serializer:

class ProfileDTOSerializer(serializers.Serializer):
id = serializers.IntegerField
first_name = serializers.CharField()
last_name = serializers.CharField()
username = serializers.CharField()
linkedin_url = serializers.URLField()
photo = serializers.ImageField()
about = serializers.CharField()
rating = serializers.FloatField()
cv_file = serializers.FileField()
role_id = serializers.IntegerField()
specialization_id = serializers.IntegerField()
tools = serializers.PrimaryKeyRelatedField(queryset=Tools.objects.all(), many=True)
Antony_K
  • 75
  • 7

2 Answers2

1

Do I get it right, that you query the posts successfully, use a serializer to get the json

{
    "post": {
        "id": 25,
        "profile_id": 2,
        "project_id": 1,
        "title": "STATUS  TEST",
        "body": "testing filtering",
        "image_url": null,
        "tags": [],
        "date_published": "2023-07-06T12:44:37.134234Z",
        "users_liked": [
            "2",
            "4"
        ],
        "likes_count": 2,
        "comments_count": 0,
        "is_active": false
    }
}

and now you just want the

"users_liked": [
    "2",
    "4"
],

section instead looking like

"users_liked": [
    {"id": 2, "username": "hello_world", "photo": "www.url.com"},
    {"id": 4, "username": "my_username", "photo": "www.another.com"}
]

If that is correct, you just need to adjust your serializer for the posts. You need to have a serializer for your User model, that is serializing the model to {"id": 2, "username": "hello_world", "photo": "www.url.com"}. If you have that Serializer (e.g. UserSerializer), you can adjust your PostSerializer like followed:

from rest_framework import serializers

class PostSerializer(serializers.ModelSerializer):
    users_liked = UserSerializer(many=True, read_only=True) // This will serialize the ids to the json objects

    class Meta:
        model = Post
        fields = '__all__'

Let me know if that helped, if you need more assistance or if this is not at all what you asked about! :)

Geilmaker
  • 471
  • 1
  • 7
  • Thank You for a quick response. You the point right. But it doesn't work. I've updated a question with Serializer class I use and DTO class. For this moment I got an exception: "Got AttributeError when attempting to get a value for field `first_name` on serializer `ProfileDTOSerializer`." I still dont understand how the serializer is able to get the data from an ID which represented as string. – Antony_K Jul 07 '23 at 18:33
  • At first I would recommend using the `serializers.ModelSerializer` class, if you want a model to be serialized - it helps a lot! I can't help you with the error with my current information, because I can't see the `ProfileDTOSerializer`. – Geilmaker Jul 07 '23 at 22:42
  • https://stackoverflow.com/a/39137996/17583953 In this post there is an example, how those 2 serializers could look like and interact with each other. The model knows which model is referenced in a fk-field or m2m-field. If you declare a serializer for that field, it looks up the primary key and uses the given serializer to serialize it. In your case you need to add the `many=True` like in my example, because you have an array of profiles, that liked that post. I can try to give you more detailed information for your project, but for this I would need some more code snippets to work with... – Geilmaker Jul 07 '23 at 22:47
  • I have certain restrictions regarding ModelSerializer utilzations. So basic Serializer is only option. So basically I want to get Profile objects in a Post query. That will solve the proble, so I can later serialize the Profile objects with custom Serializer with only 3 fields. – Antony_K Jul 08 '23 at 14:09
  • Is it currently possible, to give your `PostDetailDTOSerializer` a queried instance of your posts and it successfully converts it to your current json? – Geilmaker Jul 08 '23 at 14:25
  • Another option might be a second query... So after you queried your `post`, you should be able to use `post.liked_by.all()` to query the profiles, that liked the post. – Geilmaker Jul 08 '23 at 14:27
  • Yeah, already have it done with a Serializer field of Profile Serializer as You suggest and it made a trick! Thanks! – Antony_K Jul 08 '23 at 14:46
  • Awesome! Didn't know it works with the default Serializer class as parent, nice to know! – Geilmaker Jul 08 '23 at 15:01
1

Thanks to @Geilmaker for pointing on some django feature regarding serialization of a outcome data I found a solution with a third party lib. As I use a Dataclass (DTO) to transfer data to API (and vise versa) I found usefull an "auto-dataclass" lib which maps the query object(s) to existing dataclass (model object and dataclass fields names must match). Then I just used a relataion model's serializer as a field of my model's serializer and I got whole relation object data nested in my model's object as well.

Antony_K
  • 75
  • 7