Skip to content

Commit

Permalink
feat(follow users) follow and unfollow users
Browse files Browse the repository at this point in the history
- update profile model to include following functionality
- update profile serializer to add a following field to the profile serialized data
- write tests for the following functionality
- add endpoints for the follow functionality to the url.py file
- write functions to handle the follow functionlity

[finishes #165890222]
  • Loading branch information
joelmugaya committed May 10, 2019
1 parent 6614de1 commit 9daafe3
Show file tree
Hide file tree
Showing 6 changed files with 190 additions and 7 deletions.
24 changes: 24 additions & 0 deletions authors/apps/profiles/migrations/0002_follow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Generated by Django 2.1 on 2019-05-10 06:54

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('profiles', '0001_initial'),
]

operations = [
migrations.CreateModel(
name='Follow',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('followed', models.ForeignKey(default=False, on_delete=django.db.models.deletion.CASCADE, related_name='followed', to=settings.AUTH_USER_MODEL)),
('follower', models.ForeignKey(default=False, on_delete=django.db.models.deletion.CASCADE, related_name='follower', to=settings.AUTH_USER_MODEL)),
],
),
]
9 changes: 9 additions & 0 deletions authors/apps/profiles/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,12 @@ def create_user_profile(sender, instance, created, **kwargs):
def save_profile(sender, instance, **kwargs):
instance.userprofile.save()


class Follow(models.Model):
follower = models.ForeignKey(User,
related_name="follower",
on_delete=models.CASCADE,
default=False)
followed = models.ForeignKey(User, related_name="followed",
on_delete=models.CASCADE,
default=False)
14 changes: 12 additions & 2 deletions authors/apps/profiles/serializers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from rest_framework import serializers
from authors.apps.profiles.models import UserProfile
from authors.apps.profiles.models import UserProfile, Follow
from .models import User
import re

Expand All @@ -12,16 +12,26 @@ class Meta:

class UpdateProfileSerializer(serializers.ModelSerializer):
username = serializers.ReadOnlyField(source='user.username')
following = serializers.SerializerMethodField()
class Meta:
model = UserProfile
fields = (
'firstname',
'lastname',
'username',
'image',
'bio'
'bio',
'following'
)

def get_following(self, instance):
current_user = self.context['request'].user
following = False
if Follow.objects.filter(followed=instance.user,
follower=current_user).exists():
following = True
return following

def validate_username(self, username):
username1 = User.objects.filter(username=username)
if username1.exists():
Expand Down
55 changes: 55 additions & 0 deletions authors/apps/profiles/tests/test_follow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from django.urls import reverse
from rest_framework import status
from .base import BaseTestCase


class FollowTestCase(BaseTestCase):
"""Testcases for following a user."""

def test_follow_user_post(self):
"""Test start following a user."""
url = reverse('follow', kwargs={'username': 'test2'})
response = self.client.post(url, HTTP_AUTHORIZATION=self.auth_header)
self.assertEqual(response.status_code, status.HTTP_200_OK)

def test_post_follow_already_followed_user(self):
"""Test start following a user you already follow."""
url = reverse('follow', kwargs={'username': 'test2'})
self.client.post(url, HTTP_AUTHORIZATION=self.auth_header)
response = self.client.post(url, HTTP_AUTHORIZATION=self.auth_header)
self.assertEqual(response.status_code, status.HTTP_200_OK)

def test_follow_missing_user_post(self):
"""Test trying to start following a missing user."""
url = reverse('follow', kwargs={'username': 'joel'})
response = self.client.post(url, HTTP_AUTHORIZATION=self.auth_header)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

def test_delete_follow(self):
"""Test unfollowing a user"""
url = reverse('follow', kwargs={'username': 'test2'})
self.client.post(url, HTTP_AUTHORIZATION=self.auth_header)
response = self.client.delete(url, HTTP_AUTHORIZATION=self.auth_header)
self.assertEqual(response.status_code, status.HTTP_200_OK)

def test_delete_follow_of_not_followed_user(self):
"""Test unfollowing a user you are not following"""
url = reverse('follow', kwargs={'username': 'test2'})
response = self.client.delete(url, HTTP_AUTHORIZATION=self.auth_header)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

def test_list_followers_of_user(self):
"""Test list followers of a user"""
url_follow = reverse('follow', kwargs={'username': 'test2'})
self.client.post(url_follow, HTTP_AUTHORIZATION=self.auth_header)
url_followers = reverse('getfollowers', kwargs={'username': 'test2'})
response = self.client.get(url_followers, HTTP_AUTHORIZATION=self.auth_header)
self.assertEqual(response.status_code, status.HTTP_200_OK)

def test_list_user_is_following(self):
"""Test list users the user is following"""
url_follow = reverse('follow', kwargs={'username': 'test2'})
self.client.post(url_follow, HTTP_AUTHORIZATION=self.auth_header)
url_following = reverse('getfollowing', kwargs={'username': 'test1'})
response = self.client.get(url_following, HTTP_AUTHORIZATION=self.auth_header)
self.assertEqual(response.status_code, status.HTTP_200_OK)
12 changes: 10 additions & 2 deletions authors/apps/profiles/urls.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
from django.urls import path
from .views import UserProfiles, Updateprofile
from .views import UserProfiles, Updateprofile, \
FollowView, Followers, Following

urlpatterns = [
path('users/profiles/', UserProfiles.as_view(), name="get_profile"),
path('users/profile/<str:username>/', Updateprofile.as_view(), name="update_profile")
path('users/profile/<str:username>/', Updateprofile.as_view(),
name="update_profile"),
path('profiles/<str:username>/follow/', FollowView.as_view(),
name="follow"),
path('profiles/<str:username>/followers/', Followers.as_view(),
name="getfollowers"),
path('profiles/<str:username>/following/', Following.as_view(),
name="getfollowing")
]
83 changes: 80 additions & 3 deletions authors/apps/profiles/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
from rest_framework.response import Response
from django.shortcuts import get_object_or_404
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.exceptions import NotFound
from .renderers import ProfileJSONRenderer
from .models import UserProfile
from .models import UserProfile, Follow
from .models import User


Expand Down Expand Up @@ -41,13 +43,88 @@ def put(self, request, *args, **kwargs):
profile = self.get_object()
if str(profile) == str(request.user.username):
serializer = self.serializer_class(profile,
data=request.data['profile'])
data=request.data['profile'],
context={'request': request})
serializer.is_valid(raise_exception=True)
serializer.save(data=request.data)
return Response(UpdateProfileSerializer(profile).data)
return Response(serializer.data)
return Response(
data={'message':
'You have no permissions to edit others profile.'},
status=status.HTTP_403_FORBIDDEN
)

def get_user_object(username):
# get user object
try:
return User.objects.get(username=username)
except User.DoesNotExist:
raise NotFound(detail='User not found.')


class FollowView(APIView):
#Function to handle follow user functionality
permission_classes = (IsAuthenticated,)

def get_follow_object(self, follower, followed):
# Method to get a follow object
try:
return Follow.objects.get(follower=follower, followed=followed)
except Follow.DoesNotExist:
return None

def post(self, request, username):
# post to follow a user
user = get_user_object(username)
if user == request.user:
return Response({'error': 'Sorry, you can not follow your self.'},
status=status.HTTP_400_BAD_REQUEST)
if not self.get_follow_object(request.user, user):
Follow(followed=user, follower=request.user).save()
message = f"You are following {user.username}."
return Response({'message': message}, status=status.HTTP_200_OK,)

def delete(self, request, username):
# unfollow a user
user = get_user_object(username)
follow_Obj = self.get_follow_object(request.user, user)
if not follow_Obj:
return Response({'error': f'You are not following {user.username}.'},
status=status.HTTP_400_BAD_REQUEST)
follow_Obj.delete()
message = f"You have successfully unfollowed {user.username}."
return Response({'message': message}, status=status.HTTP_200_OK,)


class Followers(APIView):
""" Return users that follow the provided username"""
permission_classes = (IsAuthenticated,)

def get(self, request, username):
# get the user's followers
user = get_user_object(username)
followers = Follow.objects.filter(followed=user)
profiles = "No followers for "+username
for value in followers:
profiles = []
profile = UserProfile.objects.get(user=value.follower)
profile = FetchUserProfileSerializer(profile)
profiles.append(profile.data)
return Response(profiles, status=status.HTTP_200_OK,)


class Following(APIView):
""" Return users that the provided username follows"""
permission_classes = (IsAuthenticated,)

def get(self, request, username):
# get users this user follows
user = get_user_object(username)
followings = Follow.objects.filter(follower=user)
profiles = username+" is not following any users"
for value in followings:
profiles = []
profile = UserProfile.objects.get(user=value.followed)
profile = FetchUserProfileSerializer(profile)
profiles.append(profile.data)
return Response(profiles, status=status.HTTP_200_OK,)

0 comments on commit 9daafe3

Please sign in to comment.