[Answered ]-Fetch Django model in many to many relationship my other model

0👍

First, your model in models.py file does not need a getter function for every attribute. You can just reference the individual attribute from an object using the . notation like post.id where post is an object from the Post model.

Your models.py can just define the attributes like:

from django.db import models
from django.contrib.auth.models import User

class Post(models.Model):
    title = models.CharField(max_length=200)
    body = models.CharField(max_length=1000)
    published_date = models.DateTimeField('date published')
    created_by = models.ForeignKey(User, on_delete=models.CASCADE)

I’ve changed the naming of attributes here based on the following best practices:

  • The namespace for the model indicates post so, no need to name attributes prefixing the same name.
  • Using meaningful elaborated names like published_date and created_by so that they are easier to read and gain context for anyone.

Your through-table or join-table relationship should have a Foreign-Key relationship to both tables like:

class Like(models.Model):
    post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='likes')
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='likes')

For getting the count of likes, you don’t need the .all() method on the manager. That returns all the likes of that post (if you have 10K likes on the post, it will bring all of those in the memory), but what you really want is to count, so you can do post.likes.count(). You can also annotate the number of likes on your queryset.

The final view should look something like this:

from django.views import generic
from django.shortcuts import render
from django.http import HttpResponse, HttpResponseRedirect
from django.utils import timezone
from django.db.models import Count

from .models import Post


def index(request):
    posts = Post.objects.order_by("-published_date").annotate(num_likes=Count("likes"))
    return render(request, 'home.html', {'posts': posts})

You can then use posts directly in your template and use {{ post.num_likes }} to display the no of likes on that post.

1👍

The modeling of the Like is strange, a Like should likely refer to a single item with a single Post, so:

from django.conf import settings


class Like(models.Model):
    post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='likes')
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='likes'
    )

So a Like can refer to a combination of Post and user, and thus each Post can have multiple Likes and a user can have multiple Likes as well.

as for the the Post model, in Python typically one does not write getters: the attributes can be retrieved just as attributes. So the post model can look like:

from django.conf import settings


class Post(models.Model):
    title = models.CharField(max_length=200)
    body = models.CharField(max_length=1000)
    pub_date = models.DateTimeField('date published')
    author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)

    def __str__(self):
        return self.title

In the view, you also do not need to "serialize" the data, you can just pass the model objects to the template:

from django.db.models import Prefetch


def index(request):
    posts = Post.objects.prefetch_related(
        Prefetch('like', Like.objects.select_related('user'))
    ).order_by('-pub_date')
    return render(request, 'home.html', {'posts': posts})

In the template you just render the data:

{% for post in posts %}
    {{ post }}
    likes:
    <ul>
    {% for like in post.likes.all %}
        {{ like.user }}
    {% endfor %}
    </ul>
{% endfor %}

Leave a comment