Skip to content

Commit

Permalink
Merge dcc7e26 into 30dc464
Browse files Browse the repository at this point in the history
  • Loading branch information
kcharles52 committed Oct 16, 2018
2 parents 30dc464 + dcc7e26 commit f78ea33
Show file tree
Hide file tree
Showing 9 changed files with 273 additions and 15 deletions.
18 changes: 18 additions & 0 deletions authors/apps/articles/migrations/0004_auto_20181016_1206.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 2.1.1 on 2018-10-16 09:06

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('articles', '0003_auto_20181016_1048'),
]

operations = [
migrations.AlterField(
model_name='category',
name='title',
field=models.CharField(default='general', max_length=100),
),
]
4 changes: 2 additions & 2 deletions authors/apps/articles/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@

from django.contrib.postgres.fields import ArrayField
from authors.apps.authentication.models import User
from .utils import get_unique_slug,time
from .utils import get_unique_slug, time
from taggit.managers import TaggableManager


class Category(models.Model):
title = models.CharField(max_length=100)
title = models.CharField(max_length=100, default="general")
slug = models.SlugField(max_length=100, unique=True)

class Meta:
Expand Down
4 changes: 3 additions & 1 deletion authors/apps/articles/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class Meta:
or response, including fields specified explicitly above."""

fields = ('author', 'title', 'slug', 'description',
'body', 'created_at', 'updated_at', 'read_time', 'average_rating', 'likes', 'dislikes','tags','category')
'body', 'created_at', 'updated_at', 'read_time', 'average_rating', 'likes', 'dislikes','tags','category','favorites_count')
read_only_fields = ('slug', 'author_id',)

class TagSerializer(serializers.ModelSerializer):
Expand Down Expand Up @@ -78,3 +78,5 @@ def validate(self, data):
"likes": likes}


def get_favorites_count(self, instance):
return instance.favorited_by.count()
4 changes: 3 additions & 1 deletion authors/apps/articles/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from .views import (ArticleAPIView, ArticleAPIDetailsView,
RateArticleView, LikeArticleView, LikeAPIDetailsView,
TagListAPIView, TagRetrieveAPIView, CategoryListCreateAPIView, CategoryRetrieveAPIView)
TagListAPIView, TagRetrieveAPIView, CategoryListCreateAPIView, CategoryRetrieveAPIView, FavoriteArticleView, UnFavoriteArticleView)

app_name = "articles"

Expand All @@ -19,5 +19,7 @@
path("tags/<str:tag_name>/", TagRetrieveAPIView.as_view()),
path("categories/", CategoryListCreateAPIView.as_view()),
path("categories/<str:cat_name>/", CategoryRetrieveAPIView.as_view()),
path('articles/<str:slug>/favorite/',FavoriteArticleView.as_view(), name = "favorite"),
path('articles/<str:slug>/unfavorite/',UnFavoriteArticleView.as_view(), name = "unfavorite")

]
85 changes: 82 additions & 3 deletions authors/apps/articles/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from .exceptions import TagHasNoArticles, CatHasNoArticles
from .models import Article, LikeArticle, RateArticle, Category
from .renderers import (ArticleJSONRenderer, LikeUserJSONRenderer,
RateUserJSONRenderer,TagJSONRenderer, CategoryJSONRenderer)
RateUserJSONRenderer, TagJSONRenderer, CategoryJSONRenderer)
from .serializers import (ArticleSerializer, LikeArticleSerializer,
RateArticleSerializer, CategorySerializer)

Expand All @@ -44,9 +44,10 @@ def retrieve(self, request, *args, **kwargs):
else:
raise TagHasNoArticles("This tag currently has no articles")


class CategoryListCreateAPIView(generics.ListCreateAPIView):
""" List / Create categories """
""" List / Create categories """

queryset = Category.objects.all()
permission_classes = (AllowAny,)
serializer_class = CategorySerializer
Expand Down Expand Up @@ -188,3 +189,81 @@ def update(self, request, *args, **kwargs):
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response(serializer.data, status=status.HTTP_201_CREATED)


class RateArticleView(ListCreateAPIView):
permission_classes = (IsAuthenticated,)
renderer_classes = (RateUserJSONRenderer,)
queryset = RateArticle.objects.all()
serializer_class = RateArticleSerializer

def create(self, request, *args, **kwargs):
article_slug = get_object_or_404(Article, slug=self.kwargs['slug'])
get_rated_article = RateArticle.objects.filter(
article_id=article_slug.id, rated_by_id=request.user.id)
if article_slug.author_id == request.user.id:
return Response({"msg": "you can not rate your own article"})
if get_rated_article:
return Response({"msg": "you have already rated this article"})
rating = request.data.get('rate', {})
serializer = self.serializer_class(data=rating)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer, article_slug)
return Response(serializer.data, status=status.HTTP_201_CREATED)

def perform_create(self, serializer, article_slug):
serializer.save(rated_by=self.request.user, article=article_slug)


class FavoriteArticleView(generics.CreateAPIView):
permission_classes = (IsAuthenticated,)
serializer_class = ArticleSerializer

def create(self, request, *args, **kwargs):
profile = self.request.user.profile

try:
article = Article.objects.get(slug=self.kwargs['slug'])
if article.author_id == profile.user_id:
return Response({'error': 'You cannot favorite your own article'}, status=status.HTTP_400_BAD_REQUEST)
if profile.has_favorited(article):
return Response({'error': 'You already favorited this article'}, status=status.HTTP_400_BAD_REQUEST)
except Article.DoesNotExist:
return Response({'message': 'An article with this slug was not found.'},
status=status.HTTP_404_NOT_FOUND)

profile.favorite(article)
article.favorites_count += 1
article.save()

serializer = self.serializer_class(article)

return Response(serializer.data, status=status.HTTP_201_CREATED)


class UnFavoriteArticleView(generics.DestroyAPIView):
permission_classes = (IsAuthenticated,)
serializer_class = ArticleSerializer

def delete(self, request, *args, **kwargs):
profile = self.request.user.profile

try:
article = Article.objects.get(slug=self.kwargs['slug'])
except Article.DoesNotExist:
return Response({'message': 'An article with this slug was not found.'},
status=status.HTTP_404_NOT_FOUND)

if not profile.has_favorited(article):
return Response({'message': 'This article is not among your favorites'},
status=status.HTTP_400_BAD_REQUEST)

profile.unfavorite(article)
if article.favorites_count > 0:
article.favorites_count -= 1

article.save()

serializer = self.serializer_class(article)

return Response(serializer.data, status=status.HTTP_200_OK)
19 changes: 19 additions & 0 deletions authors/apps/profiles/migrations/0002_profile_favorites.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 2.1.1 on 2018-10-16 09:06

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('articles', '0004_auto_20181016_1206'),
('profiles', '0001_initial'),
]

operations = [
migrations.AddField(
model_name='profile',
name='favorites',
field=models.ManyToManyField(related_name='favorited_by', to='articles.Article'),
),
]
16 changes: 15 additions & 1 deletion authors/apps/profiles/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ class Profile(models.Model):
updated_at = models.DateTimeField(auto_now=True)
following = models.BooleanField(default=False)
number_of_articles = models.IntegerField(default=0)
favorites = models.ManyToManyField('articles.Article', related_name='favorited_by')


def __str__(self):
return self.user.username
Expand All @@ -24,6 +26,18 @@ def toggleFollowing(self):
return False
return True

def favorite(self, article):
"""Favorite an article"""
self.favorites.add(article)

def unfavorite(self, article):
"""Unfavorite an article"""
self.favorites.remove(article)

def has_favorited(self, article):
"""Check if user has already favorited that article"""
return self.favorites.filter(pk=article.pk).exists()


class FollowingUser(models.Model):
"""
Expand All @@ -33,7 +47,7 @@ class FollowingUser(models.Model):
"authentication.User", related_name='following_user', on_delete=models.CASCADE)
followed_user = models.ForeignKey(
"authentication.User", related_name='followed_user', on_delete=models.CASCADE)
date_added = models.DateTimeField(default=datetime.now)
date_added = models.DateTimeField(auto_now_add=True)

def save(self, **kwargs):
"""" This method saves the following """
Expand Down
25 changes: 18 additions & 7 deletions authors/apps/profiles/serializers.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,43 @@
from rest_framework import serializers
from .models import Profile, FollowingUser
from authors.apps.articles.models import Article


class ProfileSerializer(serializers.ModelSerializer):
username = serializers.CharField(source='user.username')
first_name = serializers.CharField(allow_blank=True, required=False)
last_name = serializers.CharField(allow_blank=True, required=False)
bio = serializers.CharField(allow_blank=True, required=False)


class Meta:
model = Profile
fields = ('username', 'first_name','last_name','bio','image','following','number_of_articles','created_at','updated_at')
fields = ('username', 'first_name', 'last_name', 'bio', 'image',
'following', 'number_of_articles', 'created_at', 'updated_at')
read_only_fields = ('username',)


class ProfilesSerializers(serializers.ModelSerializer):
username = serializers.CharField(source='user.username')
first_name = serializers.CharField(allow_blank=True, required=False)
last_name = serializers.CharField(allow_blank=True, required=False)
bio = serializers.CharField(allow_blank=True, required=False)
following = serializers.BooleanField()

favorites = serializers.SerializerMethodField()

class Meta:
model = Profile
fields = ('username', 'first_name','last_name','bio', 'following','image')
fields = ('username', 'first_name', 'last_name', 'bio', 'following', 'image',
'following', 'number_of_articles', 'created_at', 'updated_at', 'favorites')
read_only_fields = ('username',)

class FollowerSerializer(serializers.ModelSerializer):
def get_favorites(self, instance):
request = self.context.get('request', None)
favorites = instance.favorites.through.objects.filter(profile__user=instance.user)

return [Article.objects.get(pk=favorite.article_id).slug for favorite in favorites]

class FollowerSerializer(serializers.ModelSerializer):

class Meta:
model = FollowingUser
fields = ('following_user','followed_user','date_added')

fields = ('following_user','followed_user','date_added')
113 changes: 113 additions & 0 deletions tests/test_articles/test_favorite.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
from rest_framework.test import APIClient, APITestCase
from rest_framework import status
from ..test_authentication.test_base import BaseTest
from authors.apps.articles.models import Article
from authors.apps.authentication.models import User
from authors.apps.profiles.models import Profile

class FavoriteArticleTest(APITestCase, BaseTest):
def setUp(self):
BaseTest.__init__(self)
self.client = APIClient()
self.password = 'Ckato12345!'
self.login_data = {"user": {
"username": "ckato1",
"email": "ckato1@gmail.com",
"password": "Ckato12345!"
}}
self.login_data2 = {"user": {
"username": "ckato2",
"email": "ckato2@gmail.com",
"password": "Ckato12345!"
}}
# create first user
self.user1 = User.objects.create_user(
'ckato1', 'ckato1@gmail.com', password=self.password)
# activate account
self.user1.is_active = True
self.user1.is_verified = True
self.user1.save()
self.login_response1 = self.client.post(
"/api/users/login/", self.login_data, format='json')
self.client.credentials(
HTTP_AUTHORIZATION='Token ' + self.login_response1.data['token']
)
self.article1 = self.client.post('/api/articles/',self.create_article,
format="json")
self.assertEqual(self.article1.status_code, status.HTTP_201_CREATED)
# create second user
self.user2 = User.objects.create_user(
'ckato2', 'ckato2@gmail.com', password=self.password)
# activate account
self.user2.is_active = True
self.user2.is_verified = True
self.user2.save()
self.login_response2= self.client.post(
"/api/users/login/", self.login_data2, format='json')
self.client.credentials(
HTTP_AUTHORIZATION='Token ' + self.login_response2.data['token']
)
self.article2 = self.client.post('/api/articles/',self.create_article,
format="json")
self.assertEqual(self.article2.status_code, status.HTTP_201_CREATED)

def test_favorite_article(self):

self.client.credentials(
HTTP_AUTHORIZATION='Token ' + self.login_response1.data['token']
)
self.response = self.client.post('/api/articles/{}/favorite/'.format(self.article2.data["slug"]))
self.assertEqual(self.response.status_code, status.HTTP_201_CREATED)

def test_favorite_favorited_article(self):

self.client.credentials(
HTTP_AUTHORIZATION='Token ' + self.login_response1.data['token']
)
self.client.post('/api/articles/{}/favorite/'.format(self.article2.data["slug"]))
self.response = self.client.post('/api/articles/{}/favorite/'.format(self.article2.data["slug"]))

self.assertEqual(self.response.status_code, status.HTTP_400_BAD_REQUEST)

def test_favorite_own_article(self):

self.client.credentials(
HTTP_AUTHORIZATION='Token ' + self.login_response1.data['token']
)
self.response = self.client.post('/api/articles/{}/favorite/'.format(self.article1.data["slug"]))
self.assertEqual(self.response.status_code, status.HTTP_400_BAD_REQUEST)

def test_favorite_non_slug_article(self):

self.client.credentials(
HTTP_AUTHORIZATION='Token ' + self.login_response1.data['token']
)
self.response = self.client.post('/api/articles/{}/favorite/'.format('x-x-x'))
self.assertEqual(self.response.status_code, status.HTTP_404_NOT_FOUND)

def test_unfavorite_article(self):

self.client.credentials(
HTTP_AUTHORIZATION='Token ' + self.login_response1.data['token']
)
self.client.post('/api/articles/{}/favorite/'.format(self.article2.data["slug"]))
self.response = self.client.delete('/api/articles/{}/unfavorite/'.format(self.article2.data["slug"]))
self.assertEqual(self.response.status_code, status.HTTP_200_OK)

def test_unfavorite_unfavorited_article(self):

self.client.credentials(
HTTP_AUTHORIZATION='Token ' + self.login_response1.data['token']
)
self.client.post('/api/articles/{}/favorite/'.format(self.article2.data["slug"]))
self.client.delete('/api/articles/{}/unfavorite/'.format(self.article2.data["slug"]))
self.response = self.client.delete('/api/articles/{}/unfavorite/'.format(self.article2.data["slug"]))
self.assertEqual(self.response.status_code, status.HTTP_400_BAD_REQUEST)

def test_unfavorite_unexistent_article(self):

self.client.credentials(
HTTP_AUTHORIZATION='Token ' + self.login_response1.data['token']
)
self.response = self.client.delete('/api/articles/{}/unfavorite/'.format('x-x-x'))
self.assertEqual(self.response.status_code, status.HTTP_404_NOT_FOUND)

0 comments on commit f78ea33

Please sign in to comment.