Logic in View - best practice? Separation of concerns. Two examples to pick.

I have a question on where to put some logic inside a view.

CONCEPT: To create a social media app like Twitter. There is a user profile with follow or unfollow buttons, which depends on if you’re already following the user. If you already follow, the button says “unfollow” and “follow” otherwise.


The question is: where to put this following/unfollowing logic.

1. Models.

#models.py - Profile

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    follows = models.ManyToManyField(
        "self", related_name="followed_by", symmetrical=False, blank=True
    )
    date_modified = models.DateTimeField(User, auto_now=True)

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

2. HTML form

<!-- profile.html -->
            <form method="POST">
                {% csrf_token %}
                {% if profile in user.profile.follows.all %}
                <button class="btn btn-outline-danger" name="follow-btn" value="unfollow" type="submit">
                    Unfollow @{{profile.user.username|lower}}
                </button>
                {% else %}
                <button class="btn btn-outline-success" name="follow-btn" value="follow" type="submit">
                    Follow @{{profile.user.username|lower}}
                </button>
                {% endif %}
            </form>

3. View

Two approaches to the logic in the view.
Which one do you think is best?


A) OPTION 1: request the user profile and check the following state in the same function.

#views.py - OPTION 1

from django.shortcuts import get_object_or_404

def profile(request, pk):
    if request.user.is_authenticated:
        profile = get_object_or_404(Profile, user_id=pk)
        context = {"profile": profile}

        if request.method == "POST":
            current_user_profile = request.user.profile
            action = request.POST["follow-btn"]

            if action == "unfollow":
                current_user_profile.follows.remove(profile)
            elif action == "follow":
                current_user_profile.follows.add(profile)
            current_user_profile.save()

        return render(request, "profile.html", context)
    else:
        messages.success(request, "You must be logged in to view this page")
        return redirect("home")

B) OPTION 2: request the user profile and check the followers state in separate functions.

#views.py - OPTION 2

from django.shortcuts import get_object_or_404

def profile(request, pk):
    if request.user.is_authenticated:
        profile = get_object_or_404(Profile, user_id=pk)
        context = {"profile": profile}

        handle_follow_action(request, request.user.profile, profile)

        return render(request, "profile.html", context)
    else:
        messages.success(request, "You must be logged in to view this page")
        return redirect("home")


def handle_follow_action(request, current_user_profile, profile):
    if request.method == "POST":
        action = request.POST.get("follow-btn")

        if action == "unfollow":
            current_user_profile.follows.remove(profile)
        elif action == "follow":
            current_user_profile.follows.add(profile)
        current_user_profile.save()



My concern is: in this case the logic is small and maybe it is not too much of a deal to have it in one place. But what if I have to add tons of logic related to the profile request?

Maybe to put the logic somewhere else?


Well, thanks in advance!!

<opinion>
Create a completely separate view that accepts the necessary parameters and perform the logic in that view. I don’t see any need or benefit by trying to incorporate that logic anywhere else.
By doing so, it is likely also going to make it easier to implement this as a target for an AJAX-style call, allowing for an update without a full-page refresh.
</opinion>

1 Like

Thanks Ken for answering!!!