Context data and related_name (followers list)

On a user profile detail page, I’m trying to show two lists of users: Following & Followers. Currently, the Following list works, however the Followers list is not working; “not working” in this case means the correct number of bullet points representing the correct number of followers for that profile displays on the page, but the username that should be next to each bullet point does not.

Questions

  1. Why does the Following list work? Specifically, I’m confused because I didn’t define a context variable of following in the view. In the template, are the following parts of {% for following in profile.following.all %} <li>{{ following }}</li> referring to the following in the models or is it referring to something in the Django class DetailView or something else?

  2. I think my problem with the Followers list is in defining the "followers_list" context and/or referencing the right things in the <h5>Followers (TK)</h5> section of the template. Regarding the "followers_list" context, I’m confused about using the model’s related_name="followed_by". Specifically, when calling it, is the related_name supposed to go after the model it’s located in (in this case, Profile) or the model it’s got the relationship to (in this case, CustomUser) or after the model variable its associated with (in this case, following)? Or could you access it multiple ways?

View

class ProfileDetailView(DetailView):
    model = Profile
    context_object_name = "profile"  
    template_name = "profiles/profile_detail.html" 

    def get_object(self, **kwargs):
        pk = self.kwargs.get('pk')
        view_profile = Profile.objects.get(pk=pk)
        return view_profile
    
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        view_profile = self.get_object()

        context["followers_list"] = view_profile.user.followed_by.all()

Models

class CustomUser(AbstractUser):
    pass
class Profile(models.Model):
    id = models.UUIDField( 
        primary_key=True,
        default=uuid.uuid4,
        editable=False)
    user = models.OneToOneField(CustomUser, on_delete=models.CASCADE)
    following = models.ManyToManyField(CustomUser, related_name="followed_by", symmetrical=False, blank=True)

Template

<h5>Following</h5>
{% for following in profile.following.all %}
     <li>{{ following }}</li>
{% endfor %}
<h5>Followers (TK)</h5>
{% for followers in followers_list %}
    <li>{{ followers.username }}</li>
{% endfor %}

In the context, profile is an object of type Profile.

If you look at the Profile model, following is the ManyToManyField referencing CustomUser.

So the expression profile.following.all in the template is a QuerySet for all instances of CustomUser related by the following field.

The {{ following }} then is a reference to the CustomUser object. If you want to render a field within CustomUser, it might be something like {{ following.username }}, just like you’re referencing in the followers list below it.

Regarding your second question, the related_name is your “backward reference” - as you’ve named it, some_custom_user.followed_by.all is the expression that would return all of the Profile objects that are referencing that some_custom_user in each of their (those Profiles) following fields.

Thanks, Ken.

Q1 - I think I understand the first part regarding the Following list; to double-check, does the below look right?

In particular, am I understanding correctly that the profile in profile.following.all is because the context_object_name in ProfileDetailView is profile?

Q2 - For the Followers list, I think the below is the closest I got, but it returns an empty list for every profile which isn’t accurate.

To clarify, is the some_custom_user part in your description something I would define in the View’s context to tell Django which CustomUser to check for the Profile objects related through the related_name in the following field?

Template (V2)

<h5>Followers (TK)</h5>
{% for followers in view_profile_user.followed_by.all %}
    <li>{{ followers.username }}</li>
{% endfor %}

View (V2)
Note: everything except get_context_data is the same as before

class ProfileDetailView(DetailView):
    model = Profile
    context_object_name = "profile"  
    template_name = "profiles/profile_detail.html" 

    def get_object(self, **kwargs):
        pk = self.kwargs.get('pk')
        view_profile = Profile.objects.get(pk=pk)
        return view_profile
    
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        view_profile = self.get_object()

        context["view_profile_user"] = view_profile.user

Q1: That’s a beautiful diagram! (And yes, accurate.)
(I could pick nits and say that it’s not profile because it’s defined as context_object_name - it’s profile because you have a key in the context dict named profile containing a reference to a Profile object. It could have been added to the context in a number of different ways. But I don’t see the need to get that picky here.)

Q2:

Yes, your statement is correct.

To phrase it a different way, if you have:
a_user = CustomUser.objects.get(pk=pk)
then:
a_user.followed_by.all() is a queryset containing a list of Profile objects, where every instance of Profile in that list all have a_user as one of its references in its following field.

This means that in your Template (V2):

followers are the individual instances of Profile objects.

This means that this:

isn’t going to be correct here, because followers, being instances of Profile, don’t have an attribute named username. You would need to follow the OneToOneField link from Profile to CustomUser, making the appropriate expression:
<li>{{ followers.user.username }}</li>

Thank you so much; I think I’m understanding. And I appreciate your calling attention to the nuance re: context_object_name :slight_smile:

I got the Followers list to work - your rephrase was really helpful.

To double-check, is the added Followers part of the below accurate?

Your second for tag has:

view_profile_user.followed_by.all
                              ^^^ --- Django queryset function
                  ^^^^^^^^^^^ --- Related object manager for reverse access to  ManyToManyField

See Related objects reference | Django documentation | Django

The identifiers following and followed_by are effectively two different references to the same thing, the join table that connects the two ends of a ManyToMany relationship. (One for each direction of reference)
Profile -> following -> CustomUser
and
CustomUser -> followed_by -> Profile

1 Like

Okay, I think I’m getting it!

I’ll also add for any potential future readers that the Django doc section on following relationships backward was helpful for my understanding of the page Related objects reference

Thanks again, Ken; I appreciate your help!