Skip to content

Commit

Permalink
feat(Preference): Users should be able to like and dislike articles
Browse files Browse the repository at this point in the history
 - write unit tests
 - enable users like or dislike articles
 - enable CRUD operation on user preference
 - update README.md file

[#159952009]
  • Loading branch information
Patrick Kimanje authored and Patrick Kimanje committed Sep 21, 2018
1 parent 78174d3 commit 1337def
Show file tree
Hide file tree
Showing 5 changed files with 277 additions and 4 deletions.
38 changes: 38 additions & 0 deletions authors/apps/articles/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ class Article(models.Model):

tags = models.ManyToManyField(Tag, related_name='article_tag')

likes = models.ManyToManyField(User, related_name='like_preferences')

dislikes = models.ManyToManyField(User, related_name='dislike_preferences')

def __str__(self):
"""
:return: string
Expand All @@ -74,6 +78,40 @@ def average_rating(self):
ratings = self.scores.all().aggregate(score=Avg("score"))
return float('%.2f' % (ratings["score"] if ratings['score'] else 0))

def like(self, user):
return self.likes.add(user)

def dislike(self, user):
return self.dislikes.add(user)

def un_like(self, user):
return self.likes.remove(user)

def un_dislike(self, user):
return self.dislikes.remove(user)

def is_liking(self, user):
return self.likes.filter(pk=user.pk).exists()

def is_disliking(self, user):
return self.dislikes.filter(pk=user.pk).exists()

def is_neutral(self, user):
return not self.likes.filter(pk=user.pk).exists() and not self.dislikes.filter(pk=user.pk).exists()

def when_neutral(self, user):
return self.likes.filter(pk=user.pk).exists() and not self.dislikes.filter(pk=user.pk).exists()

# ****************************************

def like_to_dislike(self, user):
self.un_like(user)
self.dislike(user)

def dislike_to_like(self, user):
self.un_dislike(user)
self.like(user)

class Meta:
get_latest_by = 'created_at'
ordering = ['-created_at', 'author']
Expand Down
11 changes: 10 additions & 1 deletion authors/apps/articles/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,14 +121,23 @@ def to_representation(self, instance):
response['author'] = profile
return response

likes = serializers.SerializerMethodField()
dislikes = serializers.SerializerMethodField()

class Meta:
"""
class behaviours
"""
model = Article
# noinspection SpellCheckingInspection
fields = ('slug', 'title', 'description', 'body', 'created_at', 'average_rating', 'user_rating',
'updated_at', 'favorited', 'favorites_count', 'photo_url', 'author', 'tagList')
'updated_at', 'favorited', 'favorites_count', 'photo_url', 'author', 'tagList', 'likes', 'dislikes')

def get_likes(self, instance):
return instance.likes.all().count()

def get_dislikes(self, instance):
return instance.dislikes.all().count()


class PaginatedArticleSerializer(PageNumberPagination):
Expand Down
154 changes: 154 additions & 0 deletions authors/apps/articles/tests/test_like_or_dislike.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
from django.test import TestCase
from rest_framework.test import APIClient
from authors.apps.articles.models import User
import json


class LikeOrDislikeTestCase(TestCase):
def setUp(self):
self.client = APIClient()

# create first user
self.first_user = User.objects.create_user(
"username_one", "first_email@gmail.com", "password"
)
self.first_user = User.objects.get(email="first_email@gmail.com")
self.first_user.is_active = True
self.first_user.is_email_verified = True
self.first_user.save()

# create second user
self.second_user = User.objects.create_user(
"username_two", "second_email@gmail.com", "password"
)
self.second_user = User.objects.get(email="second_email@gmail.com")
self.second_user.is_active = True
self.second_user.is_email_verified = True
self.second_user.save()

# create_third user
self.third_user = User.objects.create_user(
"username_three", "third_email@gmail.com", "password"
)
self.third_user = User.objects.get(email="third_email@gmail.com")
self.third_user.is_active = True
self.third_user.is_email_verified = True
self.third_user.save()

# Login first user
self.login_first = self.client.post("/api/users/login/",
data={"user": {
"email": "first_email@gmail.com",
"password": "password",
}
},
format="json")

# Login second user
self.login_second = self.client.post("/api/users/login/",
data={"user": {
"email": "second_email@gmail.com",
"password": "password",
}
},
format="json")

# Login third user
self.login_third = self.client.post("/api/users/login/",
data={"user": {
"email": "third_email@gmail.com",
"password": "password",
}
},
format="json")
self.post_article = {
"article": {
"title": "Yet another Sand Blog",
"description": "Sand is m testing",
"body": "another that am doin test"
}
}

self.first_like = {"message": "You like this article"}
self.re_like = {"message": "You no longer like this article"}

self.first_dislike = {"message": "You dislike this article"}
self.re_dislike = {"message": "You no longer dislike this article"}

self.like_to_dislike = {'message': 'You now dislike this article'}
self.dislike_to_like = {'message': 'You now like this article'}

self.article_404 = {'detail': 'Article is not found.'}

def test_like_or_dislike_article(self):
token = self.login_first.data['token']
self.client.credentials(HTTP_AUTHORIZATION='Token ' + token)
response = self.client.post(
"/api/articles/", data=json.dumps(
self.post_article), content_type='application/json')
self.assertEqual(201, response.status_code)
self.assertIn('article', response.json())
self.assertIsInstance(response.json().get("article"), dict)

# get slug from created article
self.slug = response.json()['article']['slug']

# let username_one like this article
self.first_like_response = self.client.post("/api/articles/{}/like/".format(self.slug))
self.assertEqual(self.first_like_response.json(), self.first_like)

# let username_one re like this article
self.first_like_response = self.client.post("/api/articles/{}/like/".format(self.slug))
self.assertEqual(self.first_like_response.json(), self.re_like)

# Let username_two like this article
token = self.login_second.data['token']
self.client.credentials(HTTP_AUTHORIZATION='Token ' + token)
self.second_like_response = self.client.post("/api/articles/{}/like/".format(self.slug))
self.assertEqual(self.second_like_response.json(), self.first_like)

# lets username_two dislike the article he previously liked
self.first_like_response = self.client.post("/api/articles/{}/dislike/".format(self.slug))
self.assertEqual(self.first_like_response.json(), self.like_to_dislike)

# Let username_two re dislike this article
self.first_like_response = self.client.post("/api/articles/{}/dislike/".format(self.slug))
self.assertEqual(self.first_like_response.json(), self.re_dislike)

# lets username_two dislike the article | remember it is in the default state
self.first_like_response = self.client.post("/api/articles/{}/dislike/".format(self.slug))
self.assertEqual(self.first_like_response.json(), self.first_dislike)

# Let username_two like the article he previously disliked
self.second_like_response = self.client.post("/api/articles/{}/like/".format(self.slug))
self.assertEqual(self.second_like_response.json(), self.dislike_to_like)

token = self.login_third.data['token']
self.client.credentials(HTTP_AUTHORIZATION='Token ' + token)
self.third_like_response = self.client.post("/api/articles/{}/like/".format(self.slug))
self.assertEqual(self.third_like_response.json(), self.first_like)

# let username_three re-like this article
token = self.login_third.data['token']
self.client.credentials(HTTP_AUTHORIZATION='Token ' + token)
self.third_like_response = self.client.post("/api/articles/{}/like/".format(self.slug))
self.assertEqual(self.third_like_response.json(), self.re_like)

# Let username_three try to like an article that does not exist
token = self.login_third.data['token']
self.client.credentials(HTTP_AUTHORIZATION='Token ' + token)
self.third_like_response = self.client.post("/api/articles/404/like/")
self.assertEqual(self.third_like_response.json(), self.article_404)













6 changes: 4 additions & 2 deletions authors/apps/articles/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
from django.urls import path
from rest_framework.routers import DefaultRouter

from authors.apps.articles.views import ArticleViewSet, TagViewSet, RatingsView
from authors.apps.articles.views import ArticleViewSet, TagViewSet, RatingsView, DislikeOrUndislikeAPIView, LikeOrUnlikeAPIView

urlpatterns = [
path("<slug>/rate/", RatingsView.as_view())
path("<slug>/rate/", RatingsView.as_view()),
path("<slug>/like/", LikeOrUnlikeAPIView.as_view()),
path("<slug>/dislike/", DislikeOrUndislikeAPIView.as_view())
]

router = DefaultRouter()
Expand Down
72 changes: 71 additions & 1 deletion authors/apps/articles/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from rest_framework import status, viewsets
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.viewsets import ViewSet


Expand All @@ -17,6 +16,9 @@
ArticleSerializer, PaginatedArticleSerializer, TagSerializer)
from authors.apps.articles.permissions import IsSuperuser

from rest_framework.views import APIView
from rest_framework import generics

# noinspection PyUnusedLocal,PyMethodMayBeStatic


Expand Down Expand Up @@ -192,3 +194,71 @@ def destroy(self, request, *args, **kwargs):
self.perform_destroy(instance)
return Response(
{"message": "tag deleted successfuly"}, status=status.HTTP_204_NO_CONTENT)


def return_article(slug):
try:
article = Article.objects.get(slug__exact=slug)
return article
except Article.DoesNotExist:
raise NotFoundException("Article is not found.")


def determinant(action, slug, current_user):

article = return_article(slug)

if action == "like":
# if article.is_neutral(current_user):
# article.like(current_user)
# response = Response({"message": "You like this article"})
if article.is_disliking(current_user):
article.dislike_to_like(current_user)
return Response({"message": "You now like this article"})

# current user is re liking
article.un_like(current_user)
return Response({"message": "You no longer like this article"})

# if article.is_neutral(current_user):
# article.dislike(current_user)
# response = Response({"message": "You dislike this article"})
if article.is_liking(current_user):
article.like_to_dislike(current_user)
return Response({"message": "You now dislike this article"})

# current user is re liking
article.un_dislike(current_user)
return Response({"message": "You no longer dislike this article"})


class LikeOrUnlikeAPIView(APIView):
permission_classes = (IsAuthenticated,)

def post(self, request, slug):
article = return_article(slug)
if article.is_neutral(request.user):
return when_neutral("like", slug, request.user)
return determinant("like", slug, request.user)


class DislikeOrUndislikeAPIView(APIView):
permission_classes = (IsAuthenticated,)

def post(self, request, slug):
article = return_article(slug)
if article.is_neutral(request.user):
return when_neutral("dislike", slug, request.user)
return determinant("dislike", slug, request.user)


def when_neutral(action, slug, current_user):
article = return_article(slug)
if action == "like":
article.like(current_user)
article.save()
message = {"message": "You like this article"}
return Response(message)
article.dislike(current_user)
article.save()
return Response({"message": "You dislike this article"})

0 comments on commit 1337def

Please sign in to comment.