Search code examples
djangomanytomanyfield

why i am not getting a followed_by(followers) entry showing up on my page


i am making a twitter like clone(just to learn how things works in django) so i am basically trying to set up a many_to_many relationship. i want to add the functionality of showing 'FOLLOWED_BY' and 'FOLLOWING' to a user profile but list of 'FOLLOWED_BY' is not showing on the page please someone help me!

in the models.py i have define two relationship

user = models.OneToOneField(settings.AUTH_USER_MODEL, 
    on_delete=models.SET_NULL, related_name='profile', null=True, 
    blank=True)



following = models.ManyToManyField(settings.AUTH_USER_MODEL, 
     related_name='followed_by', blank=True)

and in the user_detail.html i have the code for how a profile should look like

this is the models.py module:

from django.conf import settings
from django.db import models

# Create your models here.

class UserProfile(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL, 
               on_delete=models.SET_NULL, related_name='profile', 
               null=True, 
                blank=True)
    following = models.ManyToManyField(settings.AUTH_USER_MODEL, 
                          related_name='followed_by', blank=True)

    def __str__(self):
        return str(self.following.all().count())

below is the code for user_detail.html file:

{% extends "base.html" %}

{% block content %}
<div  class="row">
    <div class="col-sm-3 col-xs-12" style="background-color: yellow">
        <h1>{{ object.username }}</h1>
        <p>Followers: {{ object.followed_by.count }}</p>
    </div>
<div class="col-sm-9 col-xs-12">
    <h1>Tweets</h1>
    {% for tweet in object.tweet_set.all %}
    {{ tweet }}<br/>
    {% endfor %}
   <hr/>    
    <h1>Following</h1>
    {% for user in object.profile.following.all %}
    <a href='/{{ user.username }}'>{{ user.username }}</a><br/>
    {% empty %}
    <h4>Not following any users</h4>
    {% endfor %}

   <hr/>    
    <h1>Followed By</h1>
    {% for profile in object.profile.followed_by.all %}
    <a href='/{{ profile.user.username }}'>{{ profile.user.username }}</a><br/>
    {% empty %}
    <h4>Not followed by any user</h4>
    {% endfor %}

</div>

{% endblock content %}

for user profile i am getting the FOLLOWING field as i want but FOLLOWED_BY field is not showing how can i do that (what changes should i do in my code)??


Solution

  • You defined a following field that points to the user model, not to a Profile. As a result a Profile has no followed_by relation, a User object has.

    I think it probably is better to let following point to Profile, like:

    class UserProfile(models.Model):
        user = models.OneToOneField(
            settings.AUTH_USER_MODEL, 
            on_delete=models.SET_NULL,
            related_name='profile', 
            null=True, 
            blank=True
        )
        following = models.ManyToManyField(
            'self',
            related_name='followed_by',
            symmetrical=False,
            blank=True
        )
    
        def __str__(self):
            return str(self.following.all().count())

    Then you can render this like:

    <div class="col-sm-3 col-xs-12" style="background-color: yellow">
        <h1>{{ object.username }}</h1>
        <p>Followers: {{ object.followed_by.count }}</p>
    </div>
    <div class="col-sm-9 col-xs-12">
        <h1>Tweets</h1>
        {% for tweet in object.tweet_set.all %}
            {{ tweet }}<br/>
        {% endfor %}
        <hr/>    
        <h1>Following</h1>
        {% for profile in object.profile.following.all %}
            <a href='/{{ profile.user.username }}'>{{ profile.user.username }}</a><br/>
        {% empty %}
            <h4>Not following any users</h4>
        {% endfor %}
    
        <hr/>    
        <h1>Followed By</h1>
        {% for profile in object.profile.followed_by.all %}
        <a href='/{{ profile.user.username }}'>{{ profile.user.username }}</a><br/>
        {% empty %}
           <h4>Not followed by any user</h4>
        {% endfor %}
    </div>

    Your code has however some (serious) anti-patterns. The most important one is that you should not write business logic in the template. You should use the view for that. For example you can specify in the view a context like:

    context = {
        'tweets': object.tweet_set.all()
        'followers': object.profile.following.select_related('user').all()
        'followed_by': object.profile.followed_by.select_related('user').all()
    }

    We here can also use a .select_related() [Django-doc] that will boost performance significantly, since now all the users are fetched in the same query.

    You also better use the {% url ... %} template tag [Django-doc] to construct queries. So instead of writing:

    <a href="/{{ profile.user.username }}">

    it is better to construct the query using a reverse lookup like:

    <a href="/{% url 'profile_view' username=profile.user.username %}">