diff --git a/authors/apps/article/renderers.py b/authors/apps/article/renderers.py index bb7be55..657d5c6 100644 --- a/authors/apps/article/renderers.py +++ b/authors/apps/article/renderers.py @@ -35,3 +35,4 @@ def render(self, data, media_type=None, renderer_context=None): if not status_code == status.HTTP_200_OK: return super(FavoriteJSONRenderer, self).render(data) return super(FavoriteJSONRenderer, self).render(data) + \ No newline at end of file diff --git a/authors/apps/article/serializers.py b/authors/apps/article/serializers.py index 7131beb..d66e0df 100644 --- a/authors/apps/article/serializers.py +++ b/authors/apps/article/serializers.py @@ -11,7 +11,7 @@ from authors.apps.profiles.models import UserProfile -from .models import RateArticle, Comments, CommentHistory, Favorite +from .models import RateArticle, Comments, CommentHistory, Favorite from authors.apps.profiles.serializers import ProfileListSerializer TABLE = apps.get_model('article', 'Article') diff --git a/authors/apps/article/urls.py b/authors/apps/article/urls.py index 708c47d..b4265e5 100644 --- a/authors/apps/article/urls.py +++ b/authors/apps/article/urls.py @@ -74,4 +74,5 @@ path('articles//history/', CommentHistoryAPIView.as_view(), name='comment_history'), + ] diff --git a/authors/apps/bookmark/__init__.py b/authors/apps/bookmark/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/authors/apps/bookmark/models.py b/authors/apps/bookmark/models.py new file mode 100644 index 0000000..5f8ff5c --- /dev/null +++ b/authors/apps/bookmark/models.py @@ -0,0 +1,14 @@ +from django.db import models +from authors.apps.authentication.models import User +from authors.apps.article.models import Article + +class Bookmark(models.Model): + """Bookmark model""" + article = models.ForeignKey( + Article, + related_name="bookmarked", + on_delete=models.CASCADE) + user = models.ForeignKey( + User, + related_name="bookmarks", + on_delete=models.CASCADE) diff --git a/authors/apps/bookmark/renderers.py b/authors/apps/bookmark/renderers.py new file mode 100644 index 0000000..e580c04 --- /dev/null +++ b/authors/apps/bookmark/renderers.py @@ -0,0 +1,11 @@ +import json +from rest_framework.renderers import JSONRenderer +from rest_framework import status + +class BookmarkJSONRenderer(JSONRenderer): + def render(self, data, media_type=None, renderer_context=None): + if renderer_context: + status_code = renderer_context.get('response').status_code + if not status_code == status.HTTP_200_OK: + return super(BookmarkJSONRenderer, self).render(data) + return super(BookmarkJSONRenderer, self).render(data) diff --git a/authors/apps/bookmark/serializers.py b/authors/apps/bookmark/serializers.py new file mode 100644 index 0000000..31d8256 --- /dev/null +++ b/authors/apps/bookmark/serializers.py @@ -0,0 +1,16 @@ +from rest_framework import serializers +from rest_framework.validators import UniqueTogetherValidator +from .models import Bookmark + +class BookmarkSerializer(serializers.ModelSerializer): + """Bookmark serializer class""" + class Meta: + model = Bookmark + fields = ('article', 'user') + validators = [ + UniqueTogetherValidator( + queryset=Bookmark.objects.all(), + fields=('article', 'user'), + message="Article already bookmarked" + ) + ] \ No newline at end of file diff --git a/authors/apps/bookmark/templates/__init__.py b/authors/apps/bookmark/templates/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/authors/apps/bookmark/tests/__init__.py b/authors/apps/bookmark/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/authors/apps/bookmark/tests/test_bookmark.py b/authors/apps/bookmark/tests/test_bookmark.py new file mode 100644 index 0000000..72b7090 --- /dev/null +++ b/authors/apps/bookmark/tests/test_bookmark.py @@ -0,0 +1,68 @@ +from rest_framework.reverse import reverse +from rest_framework import status + +from authors.apps.article.tests.base_like_test import BaseLikeTest + + +class Bookmark(BaseLikeTest): + + def bookmark_article(self, slug): + url = reverse('article:bookmark', args=[slug]) + response = self.client.post( + url, format="json", HTTP_AUTHORIZATION="Bearer " + self.token) + return response + + def test__user_can_bookmark(self): + """test user can bookmark""" + response = self.bookmark_article(self.slug) + self.assertIn("True", str(response.data)) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test__user_cant_delete_bookmark_not_bookmarked(self): + """test user cannot unbookmark before bookmarking""" + url = reverse('article:bookmark', args=[self.slug]) + response = self.client.delete( + url, format="json", HTTP_AUTHORIZATION="Bearer " + self.token) + self.assertIn( + "You have not bookmarked this article yet", str(response.data)) + self.assertEqual(response.status_code, status.HTTP_409_CONFLICT) + + def test__user_can_delete_bookmark(self): + """test user can delete bookmark""" + self.bookmark_article(self.slug) + url = reverse('article:bookmark', args=[self.slug]) + response = self.client.delete( + url, format="json", HTTP_AUTHORIZATION="Bearer " + self.token) + self.assertIn("unbookmarked", str(response.data)) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test__user_cant_bookmark_more_than_once(self): + """test user cant bookmark twice""" + self.bookmark_article(self.slug) + response = self.bookmark_article(self.slug) + self.assertIn( + "Article already bookmarked", str(response.data)) + + def test__user_cant_bookmark_article_not_existing(self): + """test user can bookmark""" + response = self.bookmark_article('collo-deos') + self.assertIn("That article does not exist", str(response.data)) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + + def test__user_cant_bookmark_article_with_no_authenticatin(self): + """test user cant bookmark with no authentication""" + url = reverse('article:bookmark', args=[self.slug]) + response = self.client.post( + url, format="json", HTTP_AUTHORIZATION="") + self.assertIn( + "Authentication credentials were not provided", str(response.data)) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test__user_cant_bookmark_article_with_no_authenticatin(self): + """test user cant unbookmark with no authentication""" + url = reverse('article:bookmark', args=[self.slug]) + response = self.client.post( + url, format="json", HTTP_AUTHORIZATION="") + self.assertIn( + "Authentication credentials were not provided", str(response.data)) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) diff --git a/authors/apps/bookmark/urls.py b/authors/apps/bookmark/urls.py new file mode 100644 index 0000000..553bf69 --- /dev/null +++ b/authors/apps/bookmark/urls.py @@ -0,0 +1,14 @@ +from django.urls import path +from rest_framework_swagger.views import get_swagger_view +from .views import ( + BookmarkAPIView +) + +schema_view = get_swagger_view(title="Bookmarks") + +urlpatterns = [ + + path('articles//bookmark/', + BookmarkAPIView.as_view(), + name="bookmark"), +] diff --git a/authors/apps/bookmark/views.py b/authors/apps/bookmark/views.py new file mode 100644 index 0000000..da21e95 --- /dev/null +++ b/authors/apps/bookmark/views.py @@ -0,0 +1,61 @@ +from rest_framework.exceptions import NotFound +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework import status +from rest_framework.permissions import IsAuthenticated + +from authors.apps.bookmark.renderers import BookmarkJSONRenderer +from authors.apps.bookmark.serializers import BookmarkSerializer +from authors.apps.article.serializers import ArticleSerializer +from authors.apps.article.models import Article +from .models import Bookmark + + +class BookmarkAPIView(APIView): + serializer_class = BookmarkSerializer + permission_classes = (IsAuthenticated,) + queryset = Bookmark.objects.all() + renderer_classes = (BookmarkJSONRenderer,) + + def post(self, request, slug): + """Bookmark article""" + try: + article = Article.objects.get(slug=slug) + except Article.DoesNotExist: + return Response({"Message": [ + "That article does not exist" + ]}, status.HTTP_204_NO_CONTENT) + bookmark = dict() + bookmark["user"] = request.user.id + bookmark["article"] = article.pk + serializer = self.serializer_class(data=bookmark) + serializer.is_valid(raise_exception=True) + serializer.save() + article_serializer = ArticleSerializer( + instance=article, context={'request': request}) + data = dict(article=article_serializer.data) + data["article"]["bookmarked"] = True + data["message"] = "bookmarked" + return Response(data, status.HTTP_200_OK) + + def delete(self, request, slug): + """unbookmark an article""" + try: + article = Article.objects.get(slug=slug) + except Article.DoesNotExist: + raise NotFound({"error": [ + "That article does not exist" + ]}) + try: + bookmark = Bookmark.objects.get( + user=request.user.id, article=article.pk) + except Bookmark.DoesNotExist: + return Response( + {"Message": "You have not bookmarked this article yet"}, + status.HTTP_409_CONFLICT) + bookmark.delete() + article_serializer = ArticleSerializer( + instance=article, context={'request': request}) + data = dict(article=article_serializer.data) + data["message"] = "unbookmarked" + return Response(data, status.HTTP_200_OK) diff --git a/authors/settings.py b/authors/settings.py index 8385e98..000087d 100755 --- a/authors/settings.py +++ b/authors/settings.py @@ -55,6 +55,7 @@ 'social_django', "authors.apps.article", + "authors.apps.bookmark", ] diff --git a/authors/urls.py b/authors/urls.py index ca43e1c..8636c44 100755 --- a/authors/urls.py +++ b/authors/urls.py @@ -41,4 +41,9 @@ include( ('authors.apps.report.urls', 'report'), - namespace='report'))] + namespace='report')), + path('api/', + include( + ('authors.apps.bookmark.urls', + 'report'), + namespace='bookmark'))] diff --git a/release-tasks.sh b/release-tasks.sh index 69e76f5..fa6dc3a 100755 --- a/release-tasks.sh +++ b/release-tasks.sh @@ -7,5 +7,7 @@ python3 manage.py makemigrations article python3 manage.py migrate article python3 manage.py makemigrations report python3 manage.py migrate report +python3 manage.py makemigrations bookmark +python3 manage.py migrate bookmark python3 manage.py makemigrations python3 manage.py migrate