Skip to content

Commit

Permalink
165305764-ft(favoriting):Favoriting articles
Browse files Browse the repository at this point in the history
- created models for posting,deleting and listing articles
- created tests for favoriting articles
- rebased from articles branch(ft-create-articles-CRUD-165305759)

[starts #165305764]
  • Loading branch information
James Maruhi authored and Abraham Kamau committed May 10, 2019
1 parent 3c748cb commit dfe80d4
Show file tree
Hide file tree
Showing 7 changed files with 295 additions and 6 deletions.
10 changes: 10 additions & 0 deletions authors/apps/articles/migrations/0001_initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,21 @@ class Migration(migrations.Migration):
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('image', cloudinary.models.CloudinaryField(max_length=255, verbose_name='image')),
('favorited', models.BooleanField(default=False)),
('favoritesCount', models.PositiveSmallIntegerField(default=0)),
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('tags', models.ManyToManyField(to='articles.Tag')),
],
options={
'ordering': ['created_at'],
},
),
migrations.CreateModel(
name='Favorite',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='articles.Article')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]
16 changes: 16 additions & 0 deletions authors/apps/articles/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ class Article(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
image = CloudinaryField('image')
tags = models.ManyToManyField(Tag)
favorited = models.BooleanField(default=False)
favoritesCount = models.PositiveSmallIntegerField(default=0)

class Meta:
ordering = ['created_at', ]
Expand Down Expand Up @@ -82,3 +84,17 @@ def clear_tags(self):
self.tags.remove(tag)
if not tag.articles:
tag.delete()
[self.tags.remove(tag) for tag in self.tags.all()]

def get_favorited(self):
favorite = self.favorited
favorite = True
return favorite


class Favorite(models.Model):
"""
A class model for storing user fovorites and articles
"""
user = models.ForeignKey(User, on_delete=models.CASCADE)
article = models.ForeignKey(Article, on_delete=models.CASCADE)
29 changes: 28 additions & 1 deletion authors/apps/articles/serializers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from rest_framework import serializers
from .models import Article
from .models import Article, Favorite
import json
from authors.apps.articles.models import Tag
from rest_framework.pagination import PageNumberPagination
Expand Down Expand Up @@ -45,3 +45,30 @@ def get_paginated_response(self, data):
('previous', self.get_previous_link()),
('results', data)
]))


class FavoritedArticlesSerializer(serializers.ModelSerializer):
"""
A class to fetch all favorited articles
"""
favorited = serializers.ReadOnlyField(source="get_favorited")
author = serializers.ReadOnlyField(source="get_author_details")

class Meta:
model = Article
fields = (
'slug', 'title', 'description', 'body',
'created_at', 'favorited', 'favoritesCount',
'updated_at', 'author'
)


class FavoritesSerializer(serializers.ModelSerializer):
"""
A class to serialize favorite article and user
"""
class Meta:
model = Favorite
fields = (
'user', 'article'
)
67 changes: 67 additions & 0 deletions authors/apps/articles/tests/basetests.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ def login(self, email, password):
def is_authenticated(self, email, password):
"""
Authenticate user
"""
login_details = self.login(email, password)
token = login_details.data['token']
Expand Down Expand Up @@ -145,6 +146,72 @@ def wrong_user_delete_article(self):
self.other_user
return self.client.delete(self.articles_url)

def post_favorite(self):
"""
A method to favorite an article
"""
slug = str(self.article.slug)
url = reverse("articles:favorite", args=[slug])
return self.client.post(
url,
data=json.dumps(
{
"article": self.article.id,
"user": self.user1.id
}
),
content_type="application/json"
)

def post_favorite_slug_doesnotexist(self):
"""
A method to favorite an article
"""
slug = "i-love-this"
url = reverse("article:favorite", args=[slug])
return self.client.post(
url,
data=json.dumps(
{
"article": self.article.id,
"user": self.user1.id
}
),
content_type="application/json"
)

def delete_favorite(self):
"""
A method to remove articles from favorites
"""

slug = str(self.article.slug)
url = reverse("articles:favorite", args=[slug])
return self.client.delete(
url,
content_type="application/json"
)

def delete_favorite_invalidslug(self):
"""
A method to favorite an article
"""
slug = "i-love-this"
url = reverse("articles:favorite", args=[slug])
return self.client.delete(
url,
content_type="application/json"
)

def get_favorites(self):
"""
A method to get all user favorite articles
"""
url = reverse("articles:get_favorite")
return self.client.get(
url,
content_type="application/json"
)

class TagsBaseTest(APITestCase):
"""
Expand Down
77 changes: 77 additions & 0 deletions authors/apps/articles/tests/test_favorite.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
from rest_framework import status

from authors.apps.articles.tests.basetests import BaseTest


class TestFavoriteArticle(BaseTest):
"""
Tests for favoriting articles
"""

def test_favorite_successfuly(self):
"""
Test method for successfully favoriting an article
"""
self.is_authenticated("adam@gmail.com", "@Us3r.com")
response = self.post_favorite()
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.data.get("message"), "Article added to favorites")


def test_favorite_invalid_slug(self):
"""
Test method for invalid slug
"""
self.is_authenticated("adam@gmail.com", "@Us3r.com")
response = self.post_favorite_slug_doesnotexist()
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
self.assertEqual(response.data["detail"], "Article does not exist")

def test_already_favorited(self):
"""
Test method for already favorited article
"""
self.is_authenticated("adam@gmail.com", "@Us3r.com")
self.post_favorite()
response = self.post_favorite()
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(response.data.get("errors").get("exist")[0], "Already favorited this article")

def test_delete_favorite_successfully(self):
"""
Test method for successfully deleting favorites
"""
self.is_authenticated("adam@gmail.com", "@Us3r.com")
self.post_favorite()
response = self.delete_favorite()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data.get("message"), "Article removed from favorites")

def test_delete_favorite_notfavorited(self):
"""
Test method for successfully deleting favorites
"""
self.is_authenticated("adam@gmail.com", "@Us3r.com")
response = self.delete_favorite()
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
self.assertEqual(response.data.get("errors").get("exist")[0], "You have not favorited this article")

def test_delete_favorite_invalidslug(self):
"""
Test method for successfully deleting favorites
"""
self.is_authenticated("adam@gmail.com", "@Us3r.com")
response = self.delete_favorite_invalidslug()
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
self.assertEqual(response.data["detail"], "Article does not exist")

def test_get_favorite_successfully(self):
"""
Test method for successfully deleting favorites
"""
self.is_authenticated("adam@gmail.com", "@Us3r.com")
self.post_favorite()
response = self.get_favorites()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data.get("favorites")[0].get("slug"), self.article.slug)

9 changes: 7 additions & 2 deletions authors/apps/articles/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@
from .views import (
ListCreateArticleAPIView,
RetrieveUpdateArticleAPIView,
FetchTags
FetchTags, FavoritesView, ListUserFavoriteArticlesView
)
from django.urls import include, path

app_name = 'articles'

urlpatterns = [
path('articles/', ListCreateArticleAPIView.as_view(), name='article'),
path('articles/<slug>/', RetrieveUpdateArticleAPIView.as_view(),
name='articles'),
path('tags/', FetchTags.as_view(), name="all_tags")
path('tags/', FetchTags.as_view(), name="all_tags"),
path('articles/<str:slug>/favorite/',
FavoritesView.as_view(), name='favorite'),
path('articles/favorites/me/',
ListUserFavoriteArticlesView.as_view(), name='get_favorite'),
]
93 changes: 90 additions & 3 deletions authors/apps/articles/views.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
from rest_framework.exceptions import APIException
from rest_framework.generics import ListCreateAPIView, GenericAPIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework import status, permissions
from .exceptions import ArticleNotFound
from rest_framework.permissions import IsAuthenticatedOrReadOnly, AllowAny
from .models import Article, Tag

from .models import Article, Tag, Favorite
from .serializers import (
ArticleSerializer,
add_tag_list,
ArticlePaginator
ArticlePaginator,
FavoritedArticlesSerializer,
FavoritesSerializer
)


Expand Down Expand Up @@ -179,3 +182,87 @@ def get(self, request):
status=status.HTTP_200_OK
)
return response


class FavoritesView(GenericAPIView):
"""
A class for posting favourite articles
"""
permission_classes = [permissions.IsAuthenticated]
serializer_class = FavoritesSerializer

def post(self, request, slug):
"""
A method to favorite an article
"""
data = request.data
article_inst = RetrieveUpdateArticleAPIView()
article = article_inst.retrieve_article(slug)
favorite_count = article.favoritesCount
user = request.user
favorite = Favorite.objects.filter(user=user, article=article)
if favorite:
response = Response({
'errors': {
'exist': ['Already favorited this article']
}
}, status=status.HTTP_400_BAD_REQUEST)
else:
Article.objects.filter(slug=slug).update(
favoritesCount=favorite_count + 1)
data['article'] = article.id
data['user'] = request.user.id
serializer = FavoritesSerializer(data=data)
serializer.is_valid()
serializer.save()
response = Response({
"article": serializer.data,
"message": "Article added to favorites"
}, status=status.HTTP_201_CREATED)

return response

def delete(self, request, slug):
"""
A method to unfavorite an article
"""
article_inst = RetrieveUpdateArticleAPIView()
article = article_inst.retrieve_article(slug)

favorite_count = article.favoritesCount
favorite = Favorite.objects.filter(
user=request.user.id, article=article)
if favorite:
favorite.delete()
Article.objects.filter(slug=slug).update(
favoritesCount=favorite_count-1)
return Response({
"message": "Article removed from favorites"
}, status=status.HTTP_200_OK)
return Response({
'errors': {
'exist': ['You have not favorited this article']
}
}, status=status.HTTP_404_NOT_FOUND)


class ListUserFavoriteArticlesView(GenericAPIView):
"""
Create Articles CRUD
"""
permission_classes = [permissions.IsAuthenticated]
serializer_class = FavoritedArticlesSerializer

def get(self, request):
favorites = Favorite.objects.filter(
user_id=request.user.id)

user_favorites = []
for favorite in favorites:
article = FavoritedArticlesSerializer(favorite.article).data
user_favorites.append(article)

return Response(
data={"favorites": user_favorites},
status=status.HTTP_200_OK
)

0 comments on commit dfe80d4

Please sign in to comment.