Skip to content

Commit

Permalink
feat(rate articles): Allow users rate articles
Browse files Browse the repository at this point in the history
- This delivers code to allow users rate articles in the api

[Delivers #161966611]
  • Loading branch information
SilasKenneth committed Dec 11, 2018
1 parent b3f8b60 commit 46170a3
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 21 deletions.
19 changes: 16 additions & 3 deletions authors/apps/articles/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from ..authentication.models import User


class Article(models.Model):
"""
Article model class.
Expand All @@ -19,7 +20,7 @@ class Article(models.Model):
# auto_now is updated with change
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

def __str__(self):
"""
Return the article title.
Expand All @@ -34,10 +35,22 @@ def create_slug(self):
while Article.objects.filter(article_slug=slug).exists():
slug = slug + '-' + uuid.uuid4().hex
return slug

def save(self, *args, **kwargs):
"""
Edit the save function to include the created slug.
"""
self.article_slug = self.create_slug()
super().save(*args,**kwargs)
super().save(*args, **kwargs)


class Rating(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
article = models.ForeignKey(Article, on_delete=models.CASCADE)
value = models.IntegerField(choices=zip(range(1, 6), (1, 6)))

class Meta:
db_table = "ratings"
# Create a unique key which is a combination of
# two fields
unique_together = ("article", "user")
23 changes: 16 additions & 7 deletions authors/apps/articles/serializers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from rest_framework import serializers

from .models import Article
from .models import Article, Rating
from ..authentication.serializers import UserSerializer


Expand All @@ -12,7 +11,7 @@ class ArticleSerializer(serializers.ModelSerializer):
description = serializers.CharField()
body = serializers.CharField()
author = serializers.HiddenField(
default = serializers.CurrentUserDefault()
default=serializers.CurrentUserDefault()
)

class Meta:
Expand All @@ -26,22 +25,32 @@ def update(self, instance, data):
instance.title = data.get('title', instance.title)
instance.description = data.get('description', instance.description)
instance.body = data.get('body', instance.body)
instance.author_id = data.get('authors_id',instance.author_id)
instance.author_id = data.get('authors_id', instance.author_id)
instance.save()
return instance

def get_author(self,Article):
def get_author(self, Article):
return Article.author.pk


class ArticleAuthorSerializer(serializers.ModelSerializer):
"""
Class to serialize article and return the full owner information.
"""
title = serializers.CharField(max_length=200)
description = serializers.CharField()
body = serializers.CharField()
author = UserSerializer(read_only = True)
author = UserSerializer(read_only=True)

class Meta:
model = Article
fields = '__all__'



class RatingSerializer(serializers.ModelSerializer):
rating = serializers.IntegerField(source='rating.value',
required=True, allow_null=False, )

class Meta:
model = Rating
fields = ('rating',)
76 changes: 76 additions & 0 deletions authors/apps/articles/tests/test_ratings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import json

from django.urls import reverse
from rest_framework.test import (APIClient,
APITestCase)
from rest_framework import status


class TestRatings(APITestCase):
def setUp(self):
self.token = self.login().get("token", "")
self.user_test = dict(
email='silaskenn@gmail.com',
username='silaskenn',
password='Password@2019'
)
self.REGISTER_URL = reverse("authentication:user-signup")
self.LOGIN_URL = reverse("authentication:user-login")
self.BASE_URL = 'http://localhost:8000/api/'
# self.RATING_URL = self.BASE_URL + "articles/good-father/rate/"
self.RATING_URL = reverse("articles:rate-article", kwargs={'slug': 'good-father'})
self.create_article()
self.client = APIClient()

def test_cannot_rate_without_token(self):
content = self.client.post(self.RATING_URL, data={'rating': 4})
self.assertEqual(content.status_code, status.HTTP_403_FORBIDDEN)

def test_cannot_rate_without_rating(self):
content = self.client.post(self.RATING_URL, data={})
self.assertEqual(content.status_code, status.HTTP_403_FORBIDDEN)
self.assertEqual(content.data.get('rating'), None)

def test_cannot_rate_with_bad_range(self):
content = self.client.post(self.RATING_URL, data={'rating': 8}, HTTP_AUTHORIZATION='Token ' + self.token)
self.assertEqual(content.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(content.data.get('errors', {}).get('rating', 'message'), 'Specify a valid rating between 1 and 5 inclusive')

def test_cannot_rate_with_non_numeric(self):
content = self.client.post(self.RATING_URL, data={'rating': 's0'}, HTTP_AUTHORIZATION='Token '+self.token)
self.assertEqual(content.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(content.data.get('errors', {}).get('rating', 'message'), 'Specify a valid rating between 1 and 5 inclusive')

def test_can_rate_article(self):
content = self.client.post(self.RATING_URL, data={'rating': 4}, HTTP_AUTHORIZATION='Token ' + self.token)
self.assertEqual(content.status_code, status.HTTP_201_CREATED)
self.assertEqual(content.data.get('article', {}).get('message', 'message')[-1:-5:-1][::-1], '4/5!')

def login(self):
self.register()
user_test = dict(user=dict(
email='silaskenn@gmail.com',
username='silaskenn',
password='Password@2019'
))
logged = self.client.post(reverse("authentication:user-login"), data=json.dumps(user_test),
content_type="application/json")
return logged.data

def register(self):
user_test = dict(user=dict(
email='silaskenn@gmail.com',
username='silaskenn',
password='Password@2019'
))
self.client.post(reverse("authentication:user-signup"), data=json.dumps(user_test),
content_type="application/json")

def create_article(self):
article = dict(article=dict(
title="Good father",
description='Something good from someone',
body='Something from the shitty mess'
))
self.client.post(reverse("articles:articles"), data=json.dumps(article),
content_type="application/json", HTTP_AUTHORIZATION='Token ' + self.token)
6 changes: 3 additions & 3 deletions authors/apps/articles/urls.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from django.urls import path

from .views import ArticleAPIView
from .views import ArticleAPIView, RatingsAPIView

urlpatterns = [
path('articles/', ArticleAPIView.as_view(), name='articles'),
path('articles/<int:pk>', ArticleAPIView.as_view(), name='articles')
path('articles/<int:pk>', ArticleAPIView.as_view(), name='articles'),
path('articles/<str:slug>/rate/', RatingsAPIView.as_view(), name='rate-article'),
]
82 changes: 75 additions & 7 deletions authors/apps/articles/views.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
from django.shortcuts import render
from rest_framework import permissions
from rest_framework.exceptions import PermissionDenied
import jwt
from django.conf import settings
from rest_framework import status
from rest_framework.views import APIView
from requests.exceptions import HTTPError
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from rest_framework.response import Response
from rest_framework.permissions import AllowAny, IsAuthenticated, IsAuthenticatedOrReadOnly
from rest_framework.views import APIView

from .permissions import IsOwnerOrReadOnly
from authors.apps.authentication.models import User
from .models import Article
from .models import (
Rating)
from .permissions import IsOwnerOrReadOnly
from .serializers import ArticleSerializer, ArticleAuthorSerializer
from .serializers import (
RatingSerializer)


class ArticleAPIView(APIView):
Expand Down Expand Up @@ -78,3 +81,68 @@ def delete(self, request, pk):
'message':"article '{}' has been successfully deleted.".format(article.title)
}
return Response(message, status=status.HTTP_200_OK)

# Create your views here.


class RatingsAPIView(APIView):
# The ratings view for handling http requests
serializer_class = RatingSerializer

def post(self, request, *args, **kwargs):
slug = kwargs.get("slug", '')
try:
article = Article.objects.get(article_slug=slug)
except Article.DoesNotExist:
article = None
if article is None:
return Response({"message": "The article with slug %s does not exist" % slug},
status=status.HTTP_404_NOT_FOUND)
else:
pass
try:
valid_user = request.user
# valid_user = User.objects.get(email=user.get('email', 'not_there'))
# First check if the token is really having an associated
# user. Because sometimes an account might be deactivated or deleted
# before the token expires which might cause the application to save
# a rating for an Invalid user
if not valid_user:
return Response({"message": "Your account is not available or might be disabled"},
status=status.HTTP_401_UNAUTHORIZED)
invalid_rating = False
try:
rating = request.data.get('rating', None)
# Incase the user never provided the rating in the request
# throw an error
if rating is None:
return Response(dict(errors=dict(
message="Missing rating field"
)))
rating = int(rating)
if not 1 <= rating <= 5:
invalid_rating = True
response = dict(errors=
dict(rating="Specify a valid rating between 1 and 5 inclusive"))
except ValueError:
response = dict(errors=
dict(rating="Specify a valid rating between 1 and 5 inclusive"))
invalid_rating = True
# Check if what was sent by the user is really a number
# if the number if None
if invalid_rating:
return Response(response, status=status.HTTP_400_BAD_REQUEST)
obje, created = Rating.objects.update_or_create(user=valid_user,
article=article,
defaults={"value": rating,
'user_id': valid_user.id,
'article_id': article.id})
obje.save()
response = dict(article={"message": "You successfully rated the article %s %d/5!"
% (article.title, obje.value)})
return Response(response, status=status.HTTP_201_CREATED)
except Exception as ex:
response = dict(errors={"message": "There was a problem sending the rating"
" try again later."})
print(ex)
return Response(response, status=status.HTTP_503_SERVICE_UNAVAILABLE)
1 change: 0 additions & 1 deletion authors/apps/authentication/tests/test_token_generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ def test_can_get_login_token(self):
data=json.dumps(self.user2),
content_type="application/json")
self.assertEqual(posted.status_code, status.HTTP_201_CREATED)
print(content.data)
self.assertEqual(content.status_code, status.HTTP_200_OK)

token_from_login = content.data.get('token', '')
Expand Down

0 comments on commit 46170a3

Please sign in to comment.