Skip to content

Commit

Permalink
Merge 061e3e4 into 2fb615c
Browse files Browse the repository at this point in the history
  • Loading branch information
RachelleMaina committed Mar 21, 2019
2 parents 2fb615c + 061e3e4 commit e6589d0
Show file tree
Hide file tree
Showing 8 changed files with 379 additions and 14 deletions.
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -479,3 +479,51 @@ No additional parameters required
### Get Tags

`GET /api/tags`

## Create an Article Rating

`POST /api/articles/<slug>/rate/`

Example request body:

```source-json
{
"rating":{
"value":"3",
"review":"Quite interesting."
}
}
```

Requires authentication

Accepted fields: `value`, `review`

## Update an Article Rating

`PUT api/articles/<slug>/rate/`

Example request body:

```source-json
{
"rating":{
"value":"4",
"review":"Quite interesting. I loved the ending."
}
}
```

Requires authentication

Accepted fields: `value`, `review`

## Get an Article's Ratings

`GET api/articles/<slug>/ratings/`


Returns all reviews attached to an article



36 changes: 36 additions & 0 deletions authors/apps/articles/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

from . import managers
from .utils import generate_unique_slug
from django.db.models import Avg
from cloudinary import CloudinaryImage


class Article(models.Model):
Expand Down Expand Up @@ -54,6 +56,12 @@ def get_reading_time(self):

return str(reading_time) + unit

def get_average_rating(self):
if Rating.objects.all().count() > 0:
rating = Rating.objects.all().aggregate(Avg('value'))
return round(rating['value__avg'], 1)
return "This article has not been rated."

class Meta:
ordering = ["-created_at", "-updated_at"]

Expand Down Expand Up @@ -129,3 +137,31 @@ class Favorite(models.Model):
related_name='favorites')
article_id = models.ForeignKey(
Article, on_delete=models.CASCADE, related_name='favorites')


class Rating(models.Model):
"""This class creates an article rating model."""

user = models.ForeignKey(
settings.AUTH_USER_MODEL,
related_name='ratings',
on_delete=models.CASCADE
)
article = models.ForeignKey(
Article,
related_name='ratings',
on_delete=models.CASCADE
)
value = models.IntegerField(null=False)
review = models.TextField(null=True, blank=True)
created = models.DateTimeField(auto_now=True)

def get_username(self):
"""This method gets the username of the user rating an article."""
return self.user.username

def get_image(self):
"""This method gets the image of the user rating an article."""
image_url = CloudinaryImage(str(self.user.profile.image)).build_url(
width=100, height=150, crop='fill')
return image_url
32 changes: 30 additions & 2 deletions authors/apps/articles/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@

from authors.apps.profiles.serializers import ProfileSerializer

from .models import Article, Favorite, Like, Snapshot, ThreadedComment
from .models import Article, Favorite, Like, Snapshot,\
ThreadedComment, Rating


class TheArticleSerializer(serializers.ModelSerializer):

reading_time = serializers.ReadOnlyField(source='get_reading_time')
average_rating = serializers.ReadOnlyField(source='get_average_rating')

class Meta:
model = Article
fields = [
'id', 'title', 'body', 'draft', 'slug', 'reading_time',
'id', 'title', 'body', 'draft', 'slug',
'reading_time', 'average_rating',
'editing', 'description', 'published', 'activated',
"created_at", "updated_at", 'author'
]
Expand Down Expand Up @@ -113,3 +116,28 @@ class Meta():
read_only_fields = ['id']


class RatingSerializer(serializers.ModelSerializer):
"""Handles serialization of article ratings."""
value = serializers.IntegerField(
min_value=1,
max_value=5,
required=True)

class Meta:
model = Rating
fields = ['user', 'article', 'value', 'review']

def get_article(self, slug):
article = Article.objects.get(slug=slug, published=True, activated=True)
return article


class ArticleRatingSerializer(serializers.ModelSerializer):
"""Handles deserialization of article ratings."""
image = serializers.ReadOnlyField(source='get_image')
username = serializers.ReadOnlyField(source='get_username')
user = {"username": username, "image": image}

class Meta:
model = Rating
fields = ['value', 'review', 'username', 'image']
10 changes: 5 additions & 5 deletions authors/apps/articles/tests/article_tests_base_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ def setUp(self):
self.like_article_url = '/api/articles/{}/like/'.format(self.slug)
self.favorite_article_url = '/api/articles/{}/favorite/'.format(self.slug)
self.get_favorites_url = reverse('articles:get_favorites')





self.rate_url = '/api/articles/{}/rate/'.format(self.slug)
self.get_rate_url = '/api/articles/{}/ratings/'.format(self.slug)
self.non_existent_article = '/api/articles/hi/rate/'
self.get_non_existent_article = '/api/articles/hi/ratings/'
self.article_rating = '/api/articles/'
91 changes: 91 additions & 0 deletions authors/apps/articles/tests/test_article_rating.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
from rest_framework import status

from .article_tests_base_class import ArticlesBaseTest
from .test_data import *
from authors.apps.articles.views import *


class ArticleRatingTestCase(ArticlesBaseTest):
"""Test like and dislike of articles"""

def test_valid_rating(self):
"""Tests for a valid rating post request."""
res = self.client.post(self.rate_url,
valid_rate_data1, **self.header_user2, format='json')
self.assertEqual(res.data, valid_data_rasponse)
self.assertEqual(res.status_code, status.HTTP_201_CREATED)

def test_rate_non_existent_article(self):
"""Tests a post request for a non-existent article."""
res = self.client.post(self.non_existent_article,
valid_rate_data1, **self.header_user2, format='json')
self.assertEqual(res.data, non_existent_article_response)
self.assertEqual(res.status_code, status.HTTP_404_NOT_FOUND)

def test_author_rating(self):
"""Tests for a post request where the user is also the article author."""
res = self.client.post(self.rate_url,
valid_rate_data1, **self.header_user1, format='json')
self.assertEqual(res.data, author_rating_data_response)
self.assertEqual(res.status_code, status.HTTP_403_FORBIDDEN)

def test_rating_twice(self):
"""Tests for a post request by a user to rate an article they have already rated."""
self.client.post(self.rate_url,
valid_rate_data1, **self.header_user2, format='json')
res = self.client.post(self.rate_url,
valid_rate_data1, **self.header_user2, format='json')
self.assertEqual(res.data, rating_twice_data_response)
self.assertEqual(res.status_code, status.HTTP_403_FORBIDDEN)

def test_reviews(self):
"""Tests for a valid get request to fetch all reviews."""
self.client.post(self.rate_url,
valid_rate_data1, **self.header_user2, format='json')
res = self.client.get(self.get_rate_url, **self.header_user2, format='json')
self.assertEqual(res.status_code, status.HTTP_200_OK)

def test_no_reviews(self):
"""Tests for a get request to fetch non-existent reviews."""
self.client.post(self.rate_url,
valid_rate_data2, **self.header_user2, format='json')
res = self.client.get(self.get_rate_url, **self.header_user2, format='json')
self.assertEqual(res.data, no_review_data_response)
self.assertEqual(res.status_code, status.HTTP_404_NOT_FOUND)

def test_article_not_rated(self):
"""Tests for an article that has not been rated when getting articles."""
res = self.client.get(self.article_rating, **self.header_user2, format='json')
self.assertEqual(res.data['results'][0]['average_rating'], no_ratings_data_response)

def test_get_non_existent_article(self):
"""Tests for a request to get the rating of a non-existent article."""
res = self.client.get(self.get_non_existent_article, **self.header_user2, format='json')
self.assertEqual(res.data, non_existent_article_response)
self.assertEqual(res.status_code, status.HTTP_404_NOT_FOUND)

def test_get_rated_article(self):
"""TTests for an article that has been rated when getting articles."""
self.client.post(self.rate_url,
valid_rate_data1, **self.header_user2, format='json')
res = self.client.get(self.article_rating, **self.header_user2, format='json')
self.assertEqual(res.data['results'][0]['average_rating'], 2.0)

def test_update_rating(self):
"""Tests for a valid request to update a rating."""
self.client.post(self.rate_url,
valid_rate_data1, **self.header_user2, format='json')
res = self.client.put(self.rate_url, update_rating_data, **self.header_user2, format='json')
self.assertEqual(res.data, update_rating_data_response)

def test_update_not_rated_article(self):
"""Tests for a valid request to update a rating."""
self.client.post(self.rate_url,
valid_rate_data1, **self.header_user1, format='json')
res = self.client.put(self.rate_url, update_rating_data, **self.header_user2, format='json')
self.assertEqual(res.data, not_rated_data_response)

def test_update_non_existent_article(self):
"""Tests for a valid request to update a rating."""
res = self.client.put(self.non_existent_article, update_rating_data, **self.header_user2, format='json')
self.assertEqual(res.data, non_existent_article_response)
50 changes: 50 additions & 0 deletions authors/apps/articles/tests/test_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,53 @@
like_data = {
"is_like": True
}

valid_rate_data1 = {
"rating": {
"value": "2",
"review": "How do you train your dragon?"
}
}

valid_rate_data2 = {
"rating": {
"value": "3",
"review": ""
}
}

update_rating_data = {
"rating": {
"value": "3",
"review": "That's great!"
}
}

valid_data_rasponse = {
"message": "Article rated."
}

author_rating_data_response = {
"message": "You cannot rate your own article."
}

no_ratings_data_response = "This article has not been rated."

non_existent_article_response = {
"message": "This article was not found."
}
no_review_data_response = {
"message": "This article has no reviews."
}

rating_twice_data_response = {
"message": "You have already rated this article."
}

update_rating_data_response = {
"message": "Rating updated."
}

not_rated_data_response ={
"message": "You have not rated this article."
}
7 changes: 7 additions & 0 deletions authors/apps/articles/urls.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from . import views
from django.urls import path


app_name = 'articles'


Expand Down Expand Up @@ -31,4 +32,10 @@

path('favorites/',
views.GetUserFavoritesView.as_view(), name="get_favorites"),
path('<slug:slug>/likes/',
views.GetArticleLikesView.as_view(), name="get_likes"),
path('<slug:slug>/rate/',
views.RatingView.as_view(), name="rate"),
path('<slug:slug>/ratings/',
views.GetRatingView.as_view(), name="get_rating")
]
Loading

0 comments on commit e6589d0

Please sign in to comment.