Skip to content

Commit

Permalink
feat(favorite-unfavroite-article): implement favorite and unfavorite …
Browse files Browse the repository at this point in the history
…of an article

 - ensure authenticated user can favorite or unfavorite an article
 - prevent unauthenticated user from favoriting or unfavoriting an article

[Starts #163383187]
  • Loading branch information
oma0256 committed Feb 11, 2019
1 parent b2b5fee commit d9236b5
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 94 deletions.
11 changes: 4 additions & 7 deletions authors/apps/articles/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,17 @@ class ArticleManager(models.Manager):
ArticleManager class is a custom Article model manager
"""

def toggle_favorite(self, user, article):
def toggle_favorite(self, user, article, is_favoriting):
"""
toggle_favorite method adds user to favorited_by if they favorite an
article or removes user from favorited_by if the unfavorite an article
"""
if user in article.favorited_by.all():
article.favorited_by.remove(user)
message = "You have unfavorited this article"
else:
if user not in article.favorited_by.all() and is_favoriting:
article.favorited_by.add(user)
message = "You have favorited this article"
if user in article.favorited_by.all() and not is_favoriting:
article.favorited_by.remove(user)
article.favoritesCount = article.favorited_by.all().count()
article.save()
return message


class Article(models.Model):
Expand Down
2 changes: 2 additions & 0 deletions authors/apps/articles/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ def get_favorited(self, obj):
This method returns True is the logged in user favorited the article
otherwise it returns False.
"""
if self.context["request"].user.is_anonymous:
return False
if self.context["request"].user.profile in obj.favorited_by.all():
return True
return False
Expand Down
90 changes: 47 additions & 43 deletions authors/apps/articles/tests/test_article_api_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from rest_framework import status

from authors.apps.articles.tests import base_class
from .test_data import test_article_data
from .test_data.test_article_data import *
from ..models import Article


Expand All @@ -23,7 +23,7 @@ def test_create_article(self):
"""
self.register_and_login_test_user()
response = self.client.post(self.articles_url,
data=test_article_data.valid_article_data,
data=valid_article_data,
format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)

Expand All @@ -34,7 +34,7 @@ def test_create_article_if_user_is_not_authenticated(self):
message
"""
response = self.client.post(self.articles_url,
data=test_article_data.valid_article_data,
data=valid_article_data,
format='json')
expected_dict = {
"detail": "Authentication credentials were not provided."
Expand Down Expand Up @@ -81,9 +81,7 @@ def test_retrieve_article_using_non_existing_slug(self):
doesnot exist
"""
response = self.client.get(reverse('articles:article-details',
kwargs={'slug':
test_article_data.
un_existing_slug}))
kwargs={'slug': un_existing_slug}))
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
expected_dict = {
'errors': 'sorry article with that slug doesnot exist'}
Expand All @@ -96,9 +94,9 @@ def test_update_an_article_authorized_user(self):
"""
self.create_article_and_authenticate_test_user()
article = Article.objects.all().first()
response = self.client.put(self.article_url(article.slug),
data=test_article_data.update_article_data,
format='json')
response = self.client.patch(self.article_url(article.slug),
data=update_article_data,
format='json')
self.assertIn('title', response.data)
self.assertEqual(response.status_code, status.HTTP_200_OK)

Expand All @@ -110,10 +108,10 @@ def test_update_an_article_unauthorized_user(self):
self.create_article_and_authenticate_test_user()
self.create_article_and_authenticate_test_user_2()
article = Article.objects.all().first()
response = self.client.put(reverse('articles:article-details',
kwargs={'slug': article.slug}),
data=test_article_data.update_article_data,
format='json')
response = self.client.patch(reverse('articles:article-details',
kwargs={'slug': article.slug}),
data=update_article_data,
format='json')
expected_dict_reponse = {
'detail': 'This article does not belong to you. Access denied'}
self.assertDictEqual(expected_dict_reponse, response.data)
Expand All @@ -126,12 +124,11 @@ def test_update_unexisting_article_slug(self):
"""
self.create_article_and_authenticate_test_user()
article = Article.objects.all().first()
response = self.client.put(reverse('articles:article-details',
kwargs={'slug':
test_article_data.
un_existing_slug}),
data=test_article_data.update_article_data,
format='json')
response = self.client.patch(reverse('articles:article-details',
kwargs={'slug': un_existing_slug}
),
data=update_article_data,
format='json')
expected_dict = {
'errors': 'sorry article with that slug doesnot exist'}
self.assertDictEqual(expected_dict, response.data)
Expand Down Expand Up @@ -171,11 +168,9 @@ def test_delete_an_article_when_user_is_authorized(self):
self.create_article_and_authenticate_test_user()
article = Article.objects.all().first()
response = self.client.delete(reverse('articles:article-details',
kwargs={'slug':
test_article_data.
un_existing_slug}),
data=test_article_data.
update_article_data,
kwargs={'slug': un_existing_slug}
),
data=update_article_data,
format='json')
expected_dict = {
'errors': 'sorry article with that slug doesnot exist'}
Expand All @@ -190,69 +185,78 @@ def create_article_and_authenticate_test_user(self):
self.client.force_authenticate(user=user)
self.create_article(user)


class TestArticleLikeDislikeArticleViews(base_class.BaseTest):
def setUp(self):
super().setUp()
self.user, self.article = self.create_article_and_authenticate_test_user()
self.user, self.article = \
self.create_article_and_authenticate_test_user()
self.slug = self.article.slug

def test_user_can_like_an_article(self):
"""a user should be able to to like an article if authenticated"""
self.assertFalse(self.article.is_liked_by(self.user))
response = self.client.post(self.like_article_url(self.article.slug))
response = self.client.post(self.like_article_url(self.slug))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertTrue(self.article.is_liked_by(self.user))

def test_user_can_dislike_an_article(self):
"""a user should be able to to like an article if authenticated"""
self.assertFalse(self.article.is_disliked_by(self.user))
response = self.client.post(self.dislike_article_url(self.article.slug))
response = self.client.post(self.dislike_article_url(self.slug))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertFalse(self.article.is_liked_by(self.user))

def test_user_can_get_like_status_for_article_they_do_not_like(self):
"""a user should get the correct like status for an article they do not like"""
response = self.client.get(self.is_liked_article_url(self.article.slug))
"""a user should get the correct like status for an article they
do not like"""
response = self.client.get(self.is_liked_article_url(self.slug))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["is_liked"], False)

def test_user_can_get_like_status_for_article_they_like(self):
"""a user should get the correct like status for an article they do like"""
"""a user should get the correct like status for an article they do
like"""
self.article.liked_by.add(self.user)
response = self.client.get(self.is_liked_article_url(self.article.slug))
response = self.client.get(self.is_liked_article_url(self.slug))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["is_liked"], True)

def test_user_can_get_dislike_status_for_article_they_do_not_dislike(self):
"""a user should get the correct like status for an article they do not dislike"""
response = self.client.get(self.is_disliked_article_url(self.article.slug))
"""a user should get the correct like status for an article they
do not dislike"""
response = self.client.get(self.is_disliked_article_url(self.slug))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["is_disliked"], False)

def test_user_can_get_dislike_status_for_article_they_dislike(self):
"""a user should get the correct like status for an article they do dislike"""
"""a user should get the correct like status for an article they do
dislike"""
self.article.disliked_by.add(self.user)
response = self.client.get(self.is_disliked_article_url(self.article.slug))
response = self.client.get(self.is_disliked_article_url(self.slug))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["is_disliked"], True)

def test_unauthorized_user_cannot_like_an_article(self):
"""a request without a valid token does not allow a user to like an article"""
"""a request without a valid token does not allow a user to like an
article"""
self.client.force_authenticate(user=None)
response = self.client.post(self.like_article_url(self.article.slug))
response = self.client.post(self.like_article_url(self.slug))
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

def test_unauthorized_user_cannot_dislike_an_article(self):
"""a request without a valid token does not allow a user to dislike an article"""
"""a request without a valid token does not allow a user to dislike an
article"""
self.client.force_authenticate(user=None)
response = self.client.post(self.dislike_article_url(self.article.slug))
response = self.client.post(self.dislike_article_url(self.slug))
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

def test_user_can_the_number_of_people_that_liked_an_article(self):
"""a user can get an article's like count"""
response = self.client.get(self.article_url(self.article.slug))
self.assertEqual(response.data['like_count'], 0) # we haven't liked any article yet
response = self.client.get(self.article_url(self.slug))
self.assertEqual(response.data['like_count'], 0)

def test_user_can_the_number_of_people_that_disliked_an_article(self):
"""a user can get an article's dislike count"""
response = self.client.get(self.article_url(self.article.slug))
self.assertEqual(response.data['dislike_count'], 0) # we haven't disliked any article yet
response = self.client.get(self.article_url(self.slug))
self.assertEqual(response.data['dislike_count'], 0)
Original file line number Diff line number Diff line change
Expand Up @@ -13,51 +13,72 @@ def setUp(self):
super().setUp()
self.user = self.activated_user()
self.article = self.create_article(self.user)
self.url = reverse('articles:article-favorite',
kwargs={"slug": self.article.slug})
self.favorite_url = reverse('articles:article-favorite',
kwargs={"slug": self.article.slug})
self.unfavorite_url = reverse('articles:article-unfavorite',
kwargs={"slug": self.article.slug})

def test_user_can_favorite_article(self):
"""
Test an authenticatated user can favourite an article
"""
self.client.force_authenticate(user=self.user)
response = self.client.get(self.url)
response = self.client.post(self.favorite_url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["article"]["favorited"], True)
self.assertEqual(response.data["article"]["favorited_by"][0],
self.user.id)
self.assertEqual(response.data["article"]["favoritesCount"], 1)
article_title = response.data["article"]["title"]
self.assertEqual(response.data["message"],
"You have favorited this article")
f"You have favorited this article {article_title}")

def test_user_can_unfavorite_article(self):
"""
Test an authenticatated user can unfavourite an article
"""
self.client.force_authenticate(user=self.user)
self.client.get(self.url)
response = self.client.get(self.url)
response = self.client.delete(self.unfavorite_url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["article"]["favorited"], False)
self.assertEqual(response.data["article"]["favorited_by"], [])
self.assertEqual(response.data["article"]["favoritesCount"], 0)
article_title = response.data["article"]["title"]
self.assertEqual(response.data["message"],
"You have unfavorited this article")
f"You have unfavorited this article {article_title}")

def test_user_cannot_favorite_non_existing_article(self):
"""
Test user cannot favourite an article that doesn't exist
"""
self.client.force_authenticate(user=self.user)
response = self.client.get(reverse('articles:article-favorite',
kwargs={"slug": "hjksgfjghs"}))
response = self.client.post(reverse('articles:article-favorite',
kwargs={"slug": "hjksgfjghs"}))
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
self.assertEqual(response.data["errors"],
'Sorry article with this slug doesnot exist')
'Article with this slug doesnot exist')

def test_user_cannot_unfavorite_non_existing_article(self):
"""
Test user cannot unfavourite an article that doesn't exist
"""
self.client.force_authenticate(user=self.user)
response = self.client.delete(reverse('articles:article-unfavorite',
kwargs={"slug": "hjksgfjghs"}))
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
self.assertEqual(response.data["errors"],
'Article with this slug doesnot exist')

def test_unauthenticated_user_cannot_favroite_article(self):
"""
Test an unauthenticatated user cannot favourite an article
"""
response = self.client.get(self.url)
response = self.client.post(self.favorite_url)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

def test_unauthenticated_user_cannot_unfavroite_article(self):
"""
Test an unauthenticatated user cannot unfavourite an article
"""
response = self.client.post(self.unfavorite_url)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
4 changes: 3 additions & 1 deletion authors/apps/articles/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@
name='articles'),
path('<slug>', views.ArticleDetailApiView.as_view(),
name='article-details'),
path('<slug>/favorite', views.ToggleFavoriteAPIView.as_view(),
path('<slug>/favorite', views.FavoriteAPIView.as_view(),
name='article-favorite'),
path('<slug>/unfavorite', views.UnFavoriteAPIView.as_view(),
name='article-unfavorite'),
path('<slug>/rate', views.ArticleRatingAPIView.as_view(),
name='article-rates'),
]
27 changes: 27 additions & 0 deletions authors/apps/articles/utils/model_helpers.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from rest_framework import status
from ..models import Article


Expand All @@ -9,3 +10,29 @@ def get_single_article_using_slug(slug):
return obj
except Article.DoesNotExist:
return None


def favorite_unfavorite_article(request, slug,
serializer_class, is_favoriting):
"""
favorite_unfavorite_article returns either a dictionary with key of
message indicating whether an article has been favorited or unfavorited,
article which has been favorited or unfavorited and a status code of 200
or it returns a dictionary with a key of errors with the appropriate status
code
"""
article = get_single_article_using_slug(slug)
user = request.user.profile
if article:
Article.objects.toggle_favorite(user, article, is_favoriting)
serializer = serializer_class(article,
context={"request": request})
title = article.title
message = f"You have favorited this article {title}" if \
is_favoriting else f"You have unfavorited this article {title}"
data = {"message": message, "article": serializer.data}
status_code = status.HTTP_200_OK
else:
data = {'errors': 'Article with this slug doesnot exist'}
status_code = status.HTTP_404_NOT_FOUND
return data, status_code
Loading

0 comments on commit d9236b5

Please sign in to comment.