Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#164046256 Users Should Follow Each Other #22

Merged
merged 1 commit into from
Mar 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions authors/apps/authentication/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from rest_framework.validators import UniqueValidator

from .models import User
from authors.apps.profiles.serializers import GetProfileSerializer
from authors.apps.profiles.serializers import ProfileSerializer


class RegistrationSerializer(serializers.ModelSerializer):
Expand Down Expand Up @@ -160,11 +160,11 @@ class UserSerializer(serializers.ModelSerializer):
min_length=8,
write_only=True
)
profile = GetProfileSerializer()
profile = ProfileSerializer()

class Meta:
model = User
fields = ('email', 'username', 'password', 'token', 'profile',)
fields = ('email', 'username', 'password', 'token', 'profile')

# The `read_only_fields` option is an alternative for explicitly
# specifying the field with `read_only=True` like we did for password
Expand Down
9 changes: 6 additions & 3 deletions authors/apps/authentication/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ def retrieve(self, request, *args, **kwargs):
# There is nothing to validate or save here. Instead, we just want the
# serializer to handle turning our `User` object into something that
# can be JSONified and sent to the client.
serializer = self.serializer_class(request.user)
serializer = self.serializer_class(
request.user, context={'current_user': request.user})

return Response(serializer.data, status=status.HTTP_200_OK)

Expand Down Expand Up @@ -155,7 +156,8 @@ def update(self, request, *args, **kwargs):
# Here is that serialize, validate, save pattern we talked about
# before.
serializer = self.serializer_class(
request.user, data=user_data, partial=True
request.user, data=user_data, partial=True,
context={'current_user': request.user}
)
serializer.is_valid(raise_exception=True)
serializer.save()
Expand Down Expand Up @@ -210,7 +212,8 @@ def create(self, request):

user.is_verified = True
user.save()
serializer = UserSerializer(user)
serializer = UserSerializer(
user, context={'current_user': request.user})
serializer.instance = user
return Response(serializer.data, status=status.HTTP_200_OK)

Expand Down
2 changes: 2 additions & 0 deletions authors/apps/profiles/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ class Profile(TimeStampModel):
website = models.URLField('website', blank=True, null=True, default='')
image = CloudinaryField(
'image', default="image/upload/v1552193974/gyzbaptikqalgthxfdnh.png") # noqa
followings = models.ManyToManyField(
'self', related_name='followers', symmetrical=False)

def __str__(self):
return self.user.username
Expand Down
6 changes: 6 additions & 0 deletions authors/apps/profiles/renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ class ProfileJSONRenderer(JSONRenderer):

def render(self, data, media_type=None, renderer_context=None):

errors = data.get('errors', None)

if errors is not None:
# As mentioned about, we will let the default JSONRenderer handle
# rendering errors.
return super().render(data)
# Finally, we can render our data under the "profile" namespace.
return json.dumps({
'profile': data
Expand Down
71 changes: 69 additions & 2 deletions authors/apps/profiles/serializers.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,88 @@
from rest_framework import serializers

from .models import Profile


class GetProfileSerializer(serializers.ModelSerializer):
class ProfileSerializer(serializers.ModelSerializer):
"""
serializers for user profile upon user registration.
"""

username = serializers.ReadOnlyField(source='get_username')
image_url = serializers.ReadOnlyField(source='get_cloudinary_url')
following = serializers.SerializerMethodField()

class Meta:
model = Profile

fields = (
'username', 'first_name', 'last_name', 'bio', 'image', 'image_url',
'website', 'city', 'phone', 'country', 'following')

read_only_fields = ("created_at", "updated_at")

def get_following(self, obj):
current_user = self.context.get('current_user', None)
following = Profile.objects.filter(
pk=current_user.pk, followings=obj.pk).exists()
return following


class MultipleProfileSerializer(serializers.ModelSerializer):
"""
serializers for user profile upon user registration.
"""

username = serializers.ReadOnlyField(source='get_username')
image_url = serializers.ReadOnlyField(source='get_cloudinary_url')

class Meta:
model = Profile

fields = (
'username', 'first_name', 'last_name', 'bio', 'image_url',
'website', 'city', 'phone', 'country')

read_only_fields = ("created_at", "updated_at")


class FollowUnfollowSerializer(serializers.ModelSerializer):
"""Serializer that returns id, username, followers, following"""

followers_total = serializers.SerializerMethodField()
following_total = serializers.SerializerMethodField()
username = serializers.ReadOnlyField(source='get_username')

class Meta:
model = Profile
fields = (
'id', 'username', 'followers_total', 'following_total',
)

def get_followers_total(self, obj):
"""Returns number of users one is following"""
return obj.followers.count()

def get_following_total(self, obj):
"""Returns total number of followers"""
return obj.followings.count()


class FollowerFollowingSerializer(serializers.ModelSerializer):
"""Serializer that return username"""
username = serializers.ReadOnlyField(source='get_username')
following = serializers.SerializerMethodField()
image_url = serializers.ReadOnlyField(source='get_cloudinary_url')

class Meta:
model = Profile
fields = (
'username', 'first_name',
'last_name', 'bio', 'image_url',
'city', 'website', 'phone',
'country', 'following')

def get_following(self, obj):
current_user = self.context.get('current_user', None)
following = Profile.objects.filter(
pk=current_user.pk, followings=obj.pk).exists()
return following
131 changes: 129 additions & 2 deletions authors/apps/profiles/tests/test_views.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from django.test import TestCase, Client
from django.urls import reverse

import json

Expand All @@ -14,13 +15,21 @@ def setUp(self):
self.username = 'testing12'
self.password = 'testuserpass'

self.email1 = 'test@gmail.com'
self.username1 = 'brian'
self.password1 = 'testuserpass'

# create a user that will be logged in
self.user = User.objects.create_user(
self.username, self.email, self.password)

self.user1 = User.objects.create_user(
self.username1, self.email1, self.password1)

# verify a user's account and save
self.user.is_verified = True
self.user.save()


self.data = {
'user': {
Expand All @@ -30,6 +39,14 @@ def setUp(self):
'callback_url': 'http://www.example.com'
}
}
self.data1 = {
'user': {
'username': 'brian',
'email': 'brian@testing.com',
'password': 'veryverystrongpassword',
}
}

self.updated_data = {

'username': "newname",
Expand Down Expand Up @@ -76,6 +93,24 @@ def register_user(self, user_details_dict):
"""
return self.test_client.post(
"/api/users/", data=json.dumps(user_details_dict), content_type='application/json')

def follow_user(self, username, token):
"""This method sends a follow request to a user"""
follow_url = reverse("profiles:follow-unfollow", kwargs={'username': username})
response = self.client.post(
follow_url,
HTTP_AUTHORIZATION=f'Bearer {token}'
)
return response

def unfollow_user(self, username, token):
"""This method sends a follow request to a user"""
follow_url = reverse("profiles:follow-unfollow", kwargs={'username': username})
response = self.client.delete(
follow_url,
HTTP_AUTHORIZATION=f'Bearer {token}'
)
return response

@property
def token(self):
Expand Down Expand Up @@ -140,7 +175,6 @@ def test_if_image_uploads_successfully(self):
self.register_user(self.data)
response = self.test_client.put(
"/api/user/", **headers, content_type='application/json', data=json.dumps(self.image_link))

self.assertEqual(response.status_code, 200)
self.assertEqual(response.json()['user']['profile']['image_url'],
"https://res.cloudinary.com/dbsri2qtr/image/upload/c_fill,h_150,w_100/cats")
Expand All @@ -155,4 +189,97 @@ def test_if_one_can_upload_pdf_as_profile_image(self):

self.assertEqual(response.status_code, 400)
self.assertEqual(response.json()['user']['image'],
"Only '.png', '.jpg', '.jpeg' files are accepted")
"Only '.png', '.jpg', '.jpeg' files are accepted")

def test_successful_follow(self):
"""Test whether API user can follow another successfully"""
token = self.login_a_user()
self.register_user(self.data1)
response = self.follow_user("brian", token)
self.assertEqual(response.json()['profile']['message'],
"You are now following brian")
self.assertEqual(response.status_code, 200)

def test_follow_unavailable_user(self):
"""Test whether API user can follow an unavailable user"""
token = self.login_a_user()
response = self.follow_user("neverheardofuser", token)
self.assertEqual(response.json()['profile']['detail'],
"The requested profile does not exist.")
self.assertEqual(response.status_code, 400)

def test_follow_yourself(self):
"""Test whether API user can follow oneself"""
token = self.login_a_user()
response = self.follow_user("testing12", token)
self.assertEqual(response.json()['errors'],
["Nice try, you cannot follow yourself"])
self.assertEqual(response.status_code, 400)

def test_unfollow_yourself(self):
"""Test whether an existing user may unfollow themseves """
token = self.login_a_user()
response = self.unfollow_user("testing12", token)
self.assertEqual(response.json()['errors'],
["Nice try, that is not possible"])
self.assertEqual(response.status_code, 400)


def test_follow_user_already_followed(self):
"""Test whether API user can follow a user they already follow"""
token = self.login_a_user()
self.register_user(self.data1)
self.follow_user("brian", token)
response = self.follow_user("brian", token)
self.assertEqual(response.json()['profile']['error'],
"You are already following brian")
self.assertEqual(response.status_code, 406)

def test_successful_unfollow(self):
"""Test whether API user can unfollow a follower successfully"""
token = self.login_a_user()
self.register_user(self.data1)
self.follow_user('brian', token)
response = self.unfollow_user('brian', token)
self.assertEqual(response.json()['profile']['message'],
"You just unfollowed brian")
self.assertEqual(response.status_code, 200)

def test_unfollow_unavailable_user(self):
"""Test whether API user can unfollow a follower successfully"""
token = self.login_a_user()
response = self.unfollow_user("UnavailableUser", token)
self.assertEqual(response.json()['profile']['detail'],
"The requested profile does not exist.")
self.assertEqual(response.status_code, 400)

def test_unfollow_nonfollower(self):
"""Test whether API user can unfollow a user they don't follow"""
token = self.login_a_user()
self.register_user(self.data1)
response = self.unfollow_user("brian", token)
self.assertEqual(response.json()['profile']['error'],
"You are not following brian")
self.assertEqual(response.status_code, 406)

def test_get_following(self):
"""Test whether API user can view follow list successfully"""
self.register_user(self.data1)
token = self.login_a_user()
headers = {'HTTP_AUTHORIZATION': 'Bearer ' + token}
self.follow_user("brian", token)
response = self.test_client.get(
"/api/profiles/brian/following/", **headers, content_type='application/json')
self.assertIsInstance(response.json()['Followers'], list)
self.assertIsInstance(response.json()['Following'], list)
self.assertEqual(response.status_code, 200)

def test_get_following_for_non_existant_user(self):
"""Test following for a non existing profile """
token = self.login_a_user()
headers = {'HTTP_AUTHORIZATION': 'Bearer ' + token}
response = self.test_client.get(
"/api/profiles/veryveryfunnyusername/following/", **headers, content_type='application/json')
self.assertEqual(response.json()['detail'],
"The requested profile does not exist.")
self.assertEqual(response.status_code, 400)
13 changes: 10 additions & 3 deletions authors/apps/profiles/urls.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
from django.urls import path

from .views import ProfileRetrieveAPIView, ProfilesListAPIView
from .views import (
ProfileRetrieveAPIView, ProfilesListAPIView,
FollowUnfollowAPIView, FollowerFollowingAPIView)

app_name = 'profiles'

urlpatterns = [
path('profiles/<username>/', ProfileRetrieveAPIView.as_view()),
path('profiles/', ProfilesListAPIView.as_view()),
path('profiles/<username>/',
ProfileRetrieveAPIView.as_view(), name='single-profile'),
path('profiles/', ProfilesListAPIView.as_view(), name='profiles'),
path('profiles/<str:username>/follow/',
FollowUnfollowAPIView.as_view(), name='follow-unfollow'),
path('profiles/<str:username>/following/',
FollowerFollowingAPIView.as_view(), name="following"),
]
Loading