fetch Django model in many to many relationship my other model

Question:

I am trying to figure out how many objects of a djanog model have a many to many relationship with a spicific objects of another model.

my models.py is

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

class Post(models.Model):
    post_title = models.CharField(max_length=200)
    post_body = models.CharField(max_length=1000)
    pub_date = models.DateTimeField('date published')
    by = models.ForeignKey(User, on_delete=models.CASCADE)
    def __str__(self):
        return self.post_title
    def get_id(self):
        return self.id
    def get_body(self):
        return self.post_body
    def get_date(self):
        return self.pub_date
    def get_name(self):
        return self.post_title
    def get_author(self):
        return self.by
    def get_likes(self):
        return type(self).likes.all()

class Like(models.Model):
    associated_post = models.ManyToManyField(Post)
    associated_user = models.ManyToManyField(User)

and my view is

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

from .models import Post

def index(request):
    posts = []
    
    for post in Post.objects.order_by('-pub_date'):
        posts.append({'title': post.get_name(), 'author_id': str(post.get_author()), 'created': post.get_date(), 'body': post.get_body(), 'id': post.get_id()})
        print(post.get_likes())
    return render(request, 'home.html', {'posts': posts})

Basically the function post.get_likes needs to return the number of likes that have a realtionship with the post.

I v’e read the django documentation on this topic, but I just can’t quite figure out what is actually going on in the example code.

Asked By: caleballen44

||

Answers:

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 %}
Answered By: Willem Van Onsem

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.

Answered By: Sanyam Khurana
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.