Follow system only showing unfollow button

Hello,
I am hoping to get help with my follower system. I have read through some of the posts on here but haven’t been able to fix it. This follow system has been created using a video on YouTube. I am not good enough to code this myself entirely yet and appreciate help in getting in working.

What I Want To Happen
When I log in I can view the profile of a member. I can hit the follow button that will update that users follower count by 1 and my following count by 1. Then when I unfollow it works in reverse.

WHAT HAPPENS
When I log into the site the UNFOLLOW button is already showing. There’s no follow button and when I click on the unfollow button I receive a 404 error
The current path, users/followers_count , didn’t match any of these.

I am hoping once the issue of why the follow button is not showing has been resolved I might be able to work on getting the system working. Hopefully resolving this will ensure the whole thing works :crossed_fingers:t5:

My url links - this one is in the app urls.py

path('users/<str:username>/followers_count', views.followers_count, name='followers_count'),

This one is in the main project urls.py

path('users/<str:username>/detail', views.follow_detail, name='user_detail'),

model.py

class FollowersCount(models.Model):
    follower = models.CharField(max_length=1000)
    user = models.CharField(max_length=1000)

    def __str__(self):
        return self.user

views.py

from django.shortcuts import render, redirect
from .models import FollowersCount

def follow_detail(request, username):
    current_user = username
    logged_in_user = request.user.username
    user_followers = len(FollowersCount.objects.filter(user=current_user))
    user_following = len(FollowersCount.objects.filter(follower=current_user))
    user_followers_list = FollowersCount.objects.filter(user=current_user).values_list('follower', flat=True)
    
    if logged_in_user in user_followers_list:
        follow_button_value = 'unfollow'
    else:
        follow_button_value = 'follow'

    return render(request, 'account/user/detail.html', {
        'current_user': current_user,
        'user_followers': user_followers,
        'user_following': user_following,
        'follow_button_value': follow_button_value
    })

def followers_count(request, username):
    if request.method == 'POST':
        value = request.POST['value']
        user = username
        follower = request.user.username
        if value == 'follow':
            FollowersCount.objects.create(follower=follower, user=user)
        else:
            try:
                followers_cnt = FollowersCount.objects.get(follower=follower, user=user)
                followers_cnt.delete()
            except FollowersCount.DoesNotExist:
                pass
        
        return redirect(f'/account/users/{user}/detail')

html page

<!--Follow button / number of followers-->
<form action="/users/{{current_user}}/followers_count" method="POST">
    {% csrf_token %}
    <input type="hidden" name="user" value="{{current_user}}" readonly/>
    <input type="hidden" name="follower" value="{{user.username}}" readonly/>
    
    {% if follow_button_value == 'follow' %}
        <input type="hidden" name="value" value="follow" readonly/>
        <button type="submit">
            Follow
        </button>
    {% else %}
        <input type="hidden" name="value" value="unfollow" readonly/>
        <button type="submit">
            Unfollow
        </button>
    {% endif %}
</form>
<div class="profile-stats">
    <ul>
        <li><span class="profile-stat-count">{{user_followers}}</span> followers</li>
        <li><span class="profile-stat-count">{{user_following}}</span> following</li>
    </ul>
</div>

Thank you, in advance for any help/suggestions/tips

Couple different things, in no particular order:

  • Suggest you replace your FollowerCount model fields with ForeignKey fields instead of CharField. It will make some of your logic easier, along with improving data integrity. (There are reasons why the other examples you find here do it this way.)

  • Also suggest as an alternative that you remove that class completely, and replace it with a ManyToManyField to self in your user model.

    • If you’re not using a custom user model, you could do this in a profile model.
    • If you want to add additional data to the relationship, such as “date_followed”, you can still use the ManyToManyField with identifying the FollowingModel as a through model.
  • Your current method of adding followers creates a couple of potential issues, including the possibility of creating multiple instances of FollowersCount for a single pair of follower and user. Once that duplication occurs, your view will fail when trying to unfollow that relationship.

  • When you want to get the number of instances where the user is following or being followed, use the count() function. Do not get the full list to count the length of the list.

  • Instead of getting the full list and checking to see if the user is in that list, change your query to see if the user=current_user and follower=logged_in_user, and use the exists() function to return the boolean value directly.

Beyond this, I’m not seeing immediately wrong here with the basic logic. My next step would be to verify the data at each step of the way.

  • When you’re first testing this, you might want to verify that the FollowersCount table is empty. Or, you may want to print your query values after getting them, to see the results in the view.

Thank you for your reply. I just need to check this with you.

  1. I should remove the FollowersCount model and add following to my Profile model instead?
class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    following = models.ManyToManyField(User, related_name='followers', blank=True)
    date_of_birth = models.DateField(blank=True, null=True)
    photo = ResizedImageField(
        size=[300, 300],
        quality=75,
        upload_to="account/",
        force_format="WEBP",
        blank=False,
    )

I will work on the view and add the count and exists() functions. I just wanted to check before I changed the model because I managed to crash the database when I went to make a change to models.py so I want to make sure this is correct.
Thanks

This will work well, yes. (Remove the blank=True from the ManyToManyField - it doesn’t make sense in this context.)

The only area where you may face some confusion is the asymmetrical object references here.

If you have an instance of User named a_user, then the list of followers of a_user would be a_user.followers.all(). However, the list of people that a_user are following will be a_user.profile.following.all().

This may be absolutely ok with you - there’s nothing technically wrong here. I’m only pointing this out as something that might cause confusion as you’re working on your code - it’s something you’ll want to keep in mind.

Ok, thanks. I will keep that in mind. So now I have

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    following = models.ManyToManyField(User, related_name='followers')
    date_of_birth = models.DateField(blank=True, null=True)
    photo = ResizedImageField(
        size=[300, 300],
        quality=75,
        upload_to="account/",
        force_format="WEBP",
        blank=False,
    )

    def __str__(self):
        return self.user.username

and in views.py

@login_required
def follow_detail(request, username):
    current_user = User.objects.get(username=username)
    logged_in_user = request.user

    user_followers_count = current_user.followers.count()
    user_following_count = logged_in_user.profile.following.count()

    is_following = logged_in_user.profile.following.filter(id=current_user.id).exists()

    follow_button_value = 'unfollow' if is_following else 'follow'

    return render(request, 'account/user/detail.html', {
        'current_user': current_user,
        'user_followers_count': user_followers_count,
        'user_following_count': user_following_count,
        'follow_button_value': follow_button_value
    })

def followers_count(request, username):
    if request.method == 'POST':
        current_user = User.objects.get(username=username)
        logged_in_user = request.user
        value = request.POST['value']

        if value == 'follow':
            logged_in_user.profile.following.add(current_user)
        else:
            logged_in_user.profile.following.remove(current_user)

        return redirect(f'/users/{username}/detail')

The html page and urls are okay as they are then? I already have a path to view the detail.html page

path('users/<username>/', views.user_detail, name='user_detail'),

Do I change that to

path('users/<str:username>/detail', views.follow_detail, name='user_detail'),

Right off-hand your code looks good. Nothing jumps out at me as being wrong.

Your url would be fine either way as long as neither the path nor the name conflict with any other occurrence in your urls.py file(s).

Side note: Using the str converter is the default and does not need to be specified in the url.

1 Like

Appreciate your help. Will make those changes and see what happens.

I have a url issue and appreciate help with it. When I use this url

path('users/<username>/', views.user_detail, name='user_detail'),

If I click on the profile of any user I see their details but an unfollow button appears on their profile and not a follow button

If I use this url

path('users/<username>/follow_detail', views.follow_detail, name='user_detail'),

when I click on a profile I see the follow button but I see my profile. I went through and clicked on a few different profiles and it brings up my user_detail with the follow button.

So now what I need is for the profile of the actual member to appear with the follow button. Then I can work on what happens when that button is clicked.

This is what I have on my html page (in case that helps)

 <!--Follow button / number of followers-->

<form action="/users/{{current_user}}/followers_count" method="POST">
    {% csrf_token %}
    <input type="hidden" name="user" value="{{current_user}}" readonly/>
    <input type="hidden" name="follower" value="{{user.username}}" readonly/>
    
    {% if follow_button_value == 'follow' %}
        <input type="hidden" name="value" value="follow" readonly/>
        <button class="btn btn-outline-success" type="submit">
            Follow
        </button>
    {% else %}
        <input type="hidden" name="value" value="unfollow" readonly/>
        <button class="btn btn-outline-danger" type="submit">
            Unfollow
        </button>
    {% endif %}
</form>
<div class="profile-stats">
    <ul>
        <li><span class="profile-stat-count">{{user_followers}}</span> followers</li>
        <li><span class="profile-stat-count">{{user_following}}</span> following</li>
    </ul>
</div>

I have something mixed up somewhere. I am seeing the profile of me the logged in user instead of the user detail of the actual user. I am just not sure what I need to change. Its probably very obvious to anyone who knows what they are doing!!!

There’s a chance that there’s some sort of conflict within your urls.py. We’d need to see the complete files (both the project-level and app level) to ensure there’s not something getting confused there.

This is the project level

from django.contrib import admin
from django.urls import path, include


urlpatterns = [
    path("admin/", admin.site.urls),
    path("account/", include("account.urls")),
    path("", include("home.urls")),
    path("locations/", include("locations.urls")),
    
]

and the app called account level

from django.urls import path, include
from django.contrib.auth import views as auth_views
from . import views

urlpatterns = [
    path('', views.dashboard, name="dashboard"),
    path('', include("django.contrib.auth.urls")),
    path("register/", views.register, name="register"),
    path("edit/", views.edit, name="edit"),
    # path('users/<username>/', views.user_detail, name='user_detail'),
    path('users/<username>/follow_detail', views.follow_detail, name='user_detail'),
    path('users/', views.user_list, name='user_list'),

]

First, I don’t see anything materially wrong with your urls. Just a couple things to be aware of:

Make sure that one or the other of these are always commented. If you’re looking to use the first one, comment the second - and vice versa.

Having the one with the include toward the top of the list is an anti-pattern. It’s possible (but not likely in this specific case) that an included url could “hide” one of your definitions below it.

Same situation with:

If you were to define a url in home.urls starting with locations/, it would hide the corresponding definitions in locations.urls.

As a result of the problems that this can cause, I usually recommend against ever matching a blank url to an include function.

I need a little more detail on your description here.

What page are you looking at when you say “when I click on a profile”? I think it may be that view and template we need to see since it is what is producing the links that you are clicking on.

The pics are helpful - but we do need to see the complete template that is generating this page.

I’m only seeing a portion of this page in the template fragment at Follow system only showing unfollow button - #8 by todiane, and I would appreciate it if you could confirm that the view at Follow system only showing unfollow button - #5 by todiane is the current version of that view - and that you don’t have any other view named follow_detail anywhere else in your source code.

Oh sorry. I didn’t realise. This is for the full list of members - list.html

{% extends "base.html" %}
{% load static %}

{% load thumbnail %}

{% block title %}Members{% endblock %}

<!-- Contains code that shows a list of members/users -->

{% block content %}
<div class="container my-5">
    <h1>Members</h1>
    {% for user in users %}
    <p>
        <div>
            <a href="{{ user.get_absolute_url }}" class="text-center">{{ user.get_full_name }}
                {% if user.profile.photo %}
                    <img src="{{ user.profile.photo.url }}" class="rounded d-block" alt="{{ user.profile.photo_alt }}">
                {% else %}
                    <img src="{% static 'images/blankavatar.png' %}" class="rounded d-block" alt="Default Avatar" style="width: 250px; height: 300px; object-fit: cover;">
                {% endif %}
            </a>
        </div>
    </p>
{% endfor %}
</div>
    {% endblock %}

and this is the detail.html page that shows the details of each member individually

{% extends "base.html" %}
{% load thumbnail %}
{% load static %}

<!-- Contains code that is added to each users profile-->
{% block title %}{{user.get_full_name}}{% endblock %}

{% block content %}
    <div class="container my-5">
        <h1>{{user.get_full_name}}</h1>

        <div class="rounded d-block">
            {% if user.profile.photo %}
                <img src="{{ user.profile.photo.url }}" alt="{{ user.profile.photo_alt }}">
            {% else %}
                <img src="{% static 'images/blankavatar.png' %}" class="rounded d-block" alt="Default Avatar" style="width: 200px; height: 200px; object-fit: cover;">
            {% endif %}
        </div>

        <!--Link to report a profile-->
        <div class="d-flex">
            <a href="#" id="report-link" data-toggle="modal" data-target="#report-modal" class="text-right">Report Profile</a>
        </div>
        <br>
        
       <!--Follow button / number of followers-->

<form action="/users/{{current_user}}/followers_count" method="POST">
    {% csrf_token %}
    <input type="hidden" name="user" value="{{current_user}}" readonly/>
    <input type="hidden" name="follower" value="{{user.username}}" readonly/>
    
    {% if follow_button_value == 'follow' %}
        <input type="hidden" name="value" value="follow" readonly/>
        <button class="btn btn-outline-success" type="submit">
            Follow
        </button>
    {% else %}
        <input type="hidden" name="value" value="unfollow" readonly/>
        <button class="btn btn-outline-danger" type="submit">
            Unfollow
        </button>
    {% endif %}
</form>
<div class="profile-stats">
    <ul>
        <li><span class="profile-stat-count">{{user_followers}}</span> followers</li>
        <li><span class="profile-stat-count">{{user_following}}</span> following</li>
    </ul>
</div>

        <!-- Report Profile Modal -->
        <div class="modal fade" id="report-modal">
            <div class="modal-dialog modal-dialog-centered">
                <div class="modal-content">
                    <div class="modal-header">
                        <h5 class="modal-title">Report Profile</h5>
                        <button type="button" class="close" data-dismiss="modal" id="close-modal">
                        <span>&times;</span>
                        </button>
                    </div>
                    <div class="modal-body">
                        <p>To report this profile to the Admin team, click here:</p>
                    </div>
                    <div class="modal-footer">
                        <button type="button" class="btn btn-primary" id="submit-report" data-dismiss="modal">Report</button>
                    </div>
                </div>
            </div>
        </div>

        <!-- the report success message -->
        <p id="report-success-message"></p>
    </div>

{% endblock %}


I definitely do not have any other view named follow_detail in my source code. I hope I have answered everything you need. I really do appreciate your time and patience :slight_smile:

1 Like

I have just realised in views.py I do have a user_detail

# to show photos that a user has uploaded on their profile


@login_required
def user_detail(request, username):
    user = get_object_or_404(User, username=username, is_active=True)
    locations = Location.objects.filter(user=user)
    context = {
        'user': user,
        'locations': locations
    }

    return render(request, 'account/user/detail.html', context)
1 Like

Just a side note - I’m going to be out for a bit, it’ll be a little while before I can get back to this. (I’m not ignoring or forgetting you.)

1 Like
from django.shortcuts import redirect
from django.http import HttpResponse
from django.shortcuts import render
from django.shortcuts import get_object_or_404
from django.contrib.auth import authenticate, login
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.contrib.auth.models import User
from django.http import JsonResponse
from django.views.decorators.http import require_POST
from .forms import LoginForm, UserRegistrationForm, \
    UserEditForm, ProfileEditForm
from .models import Profile
from locations.models import Location


# User registration form

def register(request):
    if request.method == "POST":
        user_form = UserRegistrationForm(request.POST)
        if user_form.is_valid():
            # Create a new user object but avoid saving it yet
            new_user = user_form.save(commit=False)
            # Set the chosen password
            new_user.set_password(user_form.cleaned_data["password"])
            # Save the User object
            new_user.save()
            # Create the user profile
            Profile.objects.create(user=new_user)
            return render(request, "account/register_done.html",
                          {"new_user": new_user})
    else:
        user_form = UserRegistrationForm()
    return render(request, "account/register.html", {"user_form": user_form})


# user login form

def user_login(request):
    if request.method == "POST":
        form = LoginForm(request.POST)
        if form.is_valid():
            cd = form.cleaned_data
            user = authenticate(
                request, username=cd["username"], password=cd["password"]
            )
            if user is not None:
                if user.is_active:
                    login(request, user)
                    return HttpResponse("Authenticated successfully")
                else:
                    return HttpResponse("Disabled account")
            else:
                return HttpResponse("Invalid login")
    else:
        form = LoginForm()
    return render(request, "account/login.html", {"form": form})


# User dashboard area

@login_required
def dashboard(request):
    return render(request, "account/dashboard.html", {"section": "dashboard"})


# User can edit their profile image/account

@login_required
def edit(request):
    if request.method == "POST":
        user_form = UserEditForm(instance=request.user, data=request.POST)
        profile_form = ProfileEditForm(
            instance=request.user.profile, data=request.POST,
            files=request.FILES
        )
        if user_form.is_valid() and profile_form.is_valid():
            user_form.save()
            profile_form.save()
            messages.success(request, "Profile updated " "successfully")
        else:
            messages.error(request, "Error updating your profile")
    else:
        user_form = UserEditForm(instance=request.user)
        profile_form = ProfileEditForm(instance=request.user.profile)

    return render(
        request,
        "account/edit.html",
        {"user_form": user_form, "profile_form": profile_form},
    )

# Provides a list of all members


@login_required
def user_list(request):
    users = User.objects.filter(is_active=True)
    return render(request,
                  'account/user/list.html',
                  {'section': 'people',
                   'users': users})

# to show photos that a user has uploaded on their profile


@login_required
def user_detail(request, username):
    user = get_object_or_404(User, username=username, is_active=True)
    locations = Location.objects.filter(user=user)
    context = {
        'user': user,
        'locations': locations
    }

    return render(request, 'account/user/detail.html', context)

# Display user profile information


@login_required
def user_profile(request, username):
    user = User.objects.get(username=username)

    context = {
        'user': user,
        
    }


# User follow count system

@login_required
def follow_detail(request, username):
    current_user = User.objects.get(username=username)
    logged_in_user = request.user

    user_followers_count = current_user.followers.count()
    user_following_count = logged_in_user.profile.following.count()

    is_following = logged_in_user.profile.following.filter(id=current_user.id).exists()

    follow_button_value = 'unfollow' if is_following else 'follow'

    return render(request, 'account/user/detail.html', {
        'current_user': current_user,
        'user_followers_count': user_followers_count,
        'user_following_count': user_following_count,
        'follow_button_value': follow_button_value
    })

def followers_count(request, username):
    if request.method == 'POST':
        current_user = User.objects.get(username=username)
        logged_in_user = request.user
        value = request.POST['value']

        if value == 'follow':
            logged_in_user.profile.following.add(current_user)
        else:
            logged_in_user.profile.following.remove(current_user)

        return redirect(f'/users/{username}/detail')

This is the views.py file

I will keep working on it because I feel as if I am closer than ever thanks to you!

First thing that caught my eye was in user_detail. You’ve been caught by a “Django-gotcha”.

Bottom line: Never use the identifier user as a key in the context being supplied to the rendering process.

You have:

context = {
    'user': user,
    'locations': locations
}

I’m going to guess that you also have django.contrib.auth.context_processors.auth
in the context_processors section of your TEMPLATES configuration in your settings.py file.

The problem is that the system-provided context processor will override what you define in the context with a reference to the user making the request.

So what’s happening here is that you are defining context['user'] as a reference to some user, but the Django context processor is replacing that reference with a reference to the user who is logged in and viewing that page. That’s why you’re seeing your profile and not the user who was selected.

The reason it doesn’t have the same effect on follow_detail is because you’re using current_user as the key in the context instead of user.

Django-gotcha is my new word of the day!!!

That’s something I didn’t know about.

I have been following the Django 4 by example book to help me put this together and I have this in my settings. Not sure if this is the cause of anything

ABSOLUTE_URL_OVERRIDES = {
    'auth.user': lambda u: reverse_lazy('user_detail',
                                        args=[u.username])
}

But I haven’t followed along completely because i wanted to try to code some parts myself.

In my views.py file then I need to rename a few things. Firstly the user context in user_detail and user_profile to profile_user

def user_detail(request, username):
    profile_user = get_object_or_404(User, username=username, is_active=True)
    locations = Location.objects.filter(user=profile_user)
    context = {
        'profile_user': profile_user, 
        'locations': locations
    }
    return render(request, 'account/user/detail.html', context)


def user_profile(request, username):
    profile_user = User.objects.get(username=username)  
    context = {
        'profile_user': profile_user,  
    }
    return render(request, 'account/user/detail.html', context)

I really should have picked an easier project to start with.

Anyway, thank you for your help. I will continue to try and fix this or I will stop and move on. Come back to it when other things are working.

I remember reading something in the pragmatic programmer about ‘broken windows’ being the cause of abandoned coding projects and I fully get it. :smile:

Well, whenever you come back to this, we’ll still be here to help.

1 Like