Skip to content

Commit

Permalink
feat(rating): Users should be able to rate an article
Browse files Browse the repository at this point in the history
- Enable users to rate an article authored byu others
- Enable a user to modify his rating on an article

[Finishes  #159952007]
  • Loading branch information
reiosantos authored and Innocent Asiimwe committed Sep 19, 2018
1 parent bc04755 commit e1daff1
Show file tree
Hide file tree
Showing 10 changed files with 369 additions and 26 deletions.
25 changes: 24 additions & 1 deletion authors/apps/articles/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ class to declare an article
"""

from django.db import models
from django.db.models import Avg
from django.utils import timezone

from authors.apps.articles.utils import generate_slug
Expand All @@ -17,7 +18,7 @@ class Article(models.Model):
"""
slug = models.SlugField(max_length=100, unique=True)

author = models.ForeignKey(User, on_delete=models.CASCADE)
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="articles", null=True)

title = models.CharField(max_length=255, null=False, blank=False,
error_messages={"required": "Write a short title for your article."})
Expand Down Expand Up @@ -54,6 +55,28 @@ def save(self, *args, **kwargs):

super(Article, self).save(*args, **kwargs)

@property
def average_rating(self):
"""
calculates the average rating of the article.
:return:
"""
ratings = self.scores.all().aggregate(score=Avg("score"))
return float('%.2f' % (ratings["score"] if ratings['score'] else 0))

class Meta:
get_latest_by = 'created_at'
ordering = ['-created_at', 'author']


class Rating(models.Model):
"""
Model for creating article ratings or votes
"""
article = models.ForeignKey(Article, related_name="scores", on_delete=models.CASCADE)
rated_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name="scores", null=True)
rated_at = models.DateTimeField(auto_created=True, default=timezone.now, auto_now=False)
score = models.DecimalField(max_digits=4, decimal_places=2)

class Meta:
ordering = ('-score',)
3 changes: 2 additions & 1 deletion authors/apps/articles/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
if val and isinstance(val, list) and len(val) is 1:
val = val[0]

data.update({"results": val})
if val is not None:
data.update({"results": val})

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

Expand Down
81 changes: 78 additions & 3 deletions authors/apps/articles/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@
from rest_framework.pagination import PageNumberPagination

from authors.apps.articles.exceptions import NotFoundException
from authors.apps.articles.models import Article
from authors.apps.articles.models import Article, Rating
from authors.apps.articles.utils import get_date
from authors.apps.authentication.models import User
from authors.apps.authentication.serializers import UserSerializer
from authors.apps.profiles.models import UserProfile
from authors.apps.profiles.serializers import UserProfileSerializer

Expand All @@ -19,6 +18,7 @@ class ArticleSerializer(serializers.ModelSerializer):
"""

author = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), required=False)
user_rating = serializers.CharField(source='author.average_rating', required=False)
slug = serializers.CharField(read_only=True)

def create(self, validated_data):
Expand Down Expand Up @@ -78,7 +78,7 @@ class behaviours
"""
model = Article
# noinspection SpellCheckingInspection
fields = ('slug', 'title', 'description', 'body', 'created_at',
fields = ('slug', 'title', 'description', 'body', 'created_at', 'average_rating', 'user_rating',
'updated_at', 'favorited', 'favorites_count', 'photo_url', 'author')


Expand All @@ -104,3 +104,78 @@ def get_paginated_response(self, data):
'count': self.page.paginator.count,
'results': data
}


class RatingSerializer(serializers.ModelSerializer):
"""
Define action logic for an article rating
"""
article = serializers.PrimaryKeyRelatedField(queryset=Article.objects.all())
rated_at = serializers.DateTimeField(read_only=True)
rated_by = serializers.PrimaryKeyRelatedField(queryset=User.objects.all())
score = serializers.DecimalField(required=True, max_digits=4, decimal_places=2)

@staticmethod
def update_request_data(data, slug, user: User):
"""
:param user:
:param slug:
:param data:
:return:
"""
try:
article = Article.objects.get(slug__exact=slug)
except Article.DoesNotExist:
raise NotFoundException("Article is not found.")

if article.author == user:
raise serializers.ValidationError({
"article": ["You can not rate your self"]
})

score = data.get("score", 0)
if score > 5 or score < 0:
raise serializers.ValidationError({
"score": ["Score value must not go below `0` and not go beyond `5`"]
})

data.update({"article": article.pk})
data.update({"rated_by": user.pk})
return data

def create(self, validated_data):
"""
:param validated_data:
:return:
"""
rated_by = validated_data.get("rated_by", None)
article = validated_data.get("article", None)
score = validated_data.get("score", 0)

try:
rating = Rating.objects.get(rated_by=rated_by, article__slug=article.slug)
except Rating.DoesNotExist:
return Rating.objects.create(**validated_data)

rating.score = score
rating.save()
return rating

def to_representation(self, instance):
"""
:param instance:
:return:
"""
response = super().to_representation(instance)

response['article'] = instance.article.slug
response['rated_by'] = instance.rated_by.username
response['average_rating'] = instance.article.average_rating
return response

class Meta:
"""
class behaviours
"""
model = Rating
fields = ("score", "rated_by", "rated_at", "article")
5 changes: 2 additions & 3 deletions authors/apps/articles/tests/test_articles.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
"""
import json

import unittest

from django.test import TestCase
from rest_framework import status
from rest_framework.test import APIClient

Expand All @@ -13,7 +12,7 @@
from .test_data import TestData


class Tests(unittest.TestCase, TestData):
class Tests(TestCase, TestData):

def setUp(self):
"""
Expand Down
5 changes: 2 additions & 3 deletions authors/apps/articles/tests/test_models.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
"""
test models
"""
import unittest

from django.test import TestCase
from rest_framework.test import APIClient

from authors.apps.articles.models import Article
from authors.apps.authentication.models import User


# noinspection SpellCheckingInspection
class Tests(unittest.TestCase):
class Tests(TestCase):

def setUp(self):
"""
Expand Down
Loading

0 comments on commit e1daff1

Please sign in to comment.