From f50b8f5254eab106af3dd02fd697fa361615d96b Mon Sep 17 00:00:00 2001 From: mulondo moses Date: Thu, 13 Dec 2018 18:19:43 +0300 Subject: [PATCH] feat(favorite-article): user favorites an article - The user gets a slug of an article that he/she wants to favorite. - Append it to the url. [Finishes #162163175] --- authors/apps/articles/models.py | 1 + authors/apps/articles/views.py | 12 ++- authors/apps/authentication/serializers.py | 2 + .../apps/authentication/tests/base_test.py | 82 +++++++++++++++++++ .../tests/test_reset_password.py | 30 +++++++ authors/apps/authentication/views.py | 4 +- authors/apps/profiles/models.py | 10 +-- authors/apps/profiles/serializers.py | 6 ++ authors/apps/profiles/tests/__init__.py | 0 authors/apps/profiles/tests/base_test.py | 74 +++++++++++++++++ .../profiles/tests/test_favorite_article.py | 28 +++++++ .../apps/profiles/{ => tests}/test_profile.py | 8 +- authors/apps/profiles/urls.py | 5 +- authors/apps/profiles/views.py | 38 +++++++-- 14 files changed, 278 insertions(+), 22 deletions(-) create mode 100644 authors/apps/authentication/tests/base_test.py create mode 100644 authors/apps/authentication/tests/test_reset_password.py create mode 100644 authors/apps/profiles/tests/__init__.py create mode 100644 authors/apps/profiles/tests/base_test.py create mode 100644 authors/apps/profiles/tests/test_favorite_article.py rename authors/apps/profiles/{ => tests}/test_profile.py (91%) diff --git a/authors/apps/articles/models.py b/authors/apps/articles/models.py index 0b24df1..addcdf8 100644 --- a/authors/apps/articles/models.py +++ b/authors/apps/articles/models.py @@ -2,6 +2,7 @@ from authors.apps.profiles.models import UserProfile from authors.apps.core.models import TimestampedModel from django.contrib.postgres.fields import ArrayField +from ..profiles.models import UserProfile class Article(TimestampedModel): diff --git a/authors/apps/articles/views.py b/authors/apps/articles/views.py index 8a6bf87..f6abe9e 100644 --- a/authors/apps/articles/views.py +++ b/authors/apps/articles/views.py @@ -3,10 +3,16 @@ from rest_framework.response import Response from rest_framework.exceptions import NotFound from rest_framework.generics import ( - RetrieveUpdateDestroyAPIView, ListCreateAPIView -) + RetrieveUpdateDestroyAPIView,RetrieveUpdateAPIView, + ListCreateAPIView) +from .models import Article +from authors.apps.profiles.models import UserProfile +from rest_framework.generics import ( + RetrieveUpdateDestroyAPIView,RetrieveUpdateAPIView, + ListCreateAPIView) from rest_framework.views import APIView from .models import Article, Rating + from .renderers import ArticleJSONRenderer from .serializers import ArticleSerializer, RatingSerializer from .pagination import PageNumbering @@ -70,7 +76,7 @@ def update(self, request, slug): def destroy(self, request, slug): try: serializer_instance = self.queryset.get(slug=slug) - except Article.DoesNotExist: + except Article.DoesNotExist: raise NotFound('An article with this slug does not exist.') return Response( diff --git a/authors/apps/authentication/serializers.py b/authors/apps/authentication/serializers.py index 94202e3..5ff9914 100644 --- a/authors/apps/authentication/serializers.py +++ b/authors/apps/authentication/serializers.py @@ -150,4 +150,6 @@ def update(self, instance, validated_data): class PasswordSerializer(serializers.Serializer): new_password = serializers.CharField(max_length=255, required=True) + + \ No newline at end of file diff --git a/authors/apps/authentication/tests/base_test.py b/authors/apps/authentication/tests/base_test.py new file mode 100644 index 0000000..73a7b15 --- /dev/null +++ b/authors/apps/authentication/tests/base_test.py @@ -0,0 +1,82 @@ +from django.urls import path, reverse +from rest_framework import status +from rest_framework.test import APITestCase, APIRequestFactory, APIClient +from authors.apps.authentication.views import RegistrationAPIView, AccountVerified +from authors.apps.authentication.models import User +from authors.apps.authentication.backends import JWTAuthentication + + +class BaseTest(APITestCase): + + def setUp(self): + self.slug = 0 + self.user_data = { + "user": { + "username": "minime", + "email": "alexkayabula@gmail.com", + "password": "W123456/78"}} + self.url = reverse("registration") + self.client = APIClient() + self.client.post(self.url, self.user_data, format='json') + self.request = APIRequestFactory().post( + reverse("registration") + ) + + self.new_article = { + "article": { + "title": "How to fight dragons 8", + "description": "Ever wonder jckvlahvhow?", + "body": "You have kenglto believe" + } + } + + self.update_article = { + "article": { + "title": "How to fight dragons 8", + "description": "Ever wonder jckvlahvhow?", + "body": "You have kenglto believe" + } + } + + self.article_without_title = { + "article": { + "description": "Ever wonder jckvlahvhow?", + "body": "You have kenglto believe" + } + } + + user = User.objects.get() + request = self.request + token, uid = RegistrationAPIView.generate_token(user, request) + response = self.account_verification(token, uid) + self.assertEqual(response.status_code, status.HTTP_200_OK) + user = User.objects.get() + self.assertTrue(user.is_verified) + + response = self.client.post( + '/api/users/login/', + self.user_data, + format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.login_token = response.data['token'] + self.client.credentials( + HTTP_AUTHORIZATION='Bearer ' + + self.login_token) + + def account_verification(self, token, uid): + request = APIRequestFactory().get( + reverse("verify_account", kwargs={"token": token, "uid": uid}) + ) + verify = AccountVerified.as_view() + response = verify(request, token=token, uid=uid) + return response + + def get_slug(self, response=[]): + self.client.post( + '/api/articles/', + data=self.new_article, + format='json') + response = self.client.get('/api/articles/', format='json') + for i in response.data: + self.slug = i['slug'] + return self.slug diff --git a/authors/apps/authentication/tests/test_reset_password.py b/authors/apps/authentication/tests/test_reset_password.py new file mode 100644 index 0000000..c25ed7c --- /dev/null +++ b/authors/apps/authentication/tests/test_reset_password.py @@ -0,0 +1,30 @@ +from .base_test import BaseTest +from rest_framework import status + + +class TestRestPassWord(BaseTest): + + def test_reset_password(self): + usr = { + "user": { + "username": "minime", + "email": "alexkayabula@gmail.com", + "password": "W123456/78"}} + self.client.post('/api/users/password_reset/',data = usr,format = 'json') + reset_data = { + "user": { + "username": "minime", + "email": "alexkayabula@gmail.com", + "password": "Whbdc^734"}} + response = self.client.post('/api/users/password_reset/',data = reset_data ,format = 'json') + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + def test_reset_password_with_missing_email(self): + usr = { + "user": { + "username": "minime", + "email": "", + "password": "W123456/78"}} + response = self.client.post('/api/users/password_reset/',data = usr,format = 'json') + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + diff --git a/authors/apps/authentication/views.py b/authors/apps/authentication/views.py index 51dc694..b160e64 100644 --- a/authors/apps/authentication/views.py +++ b/authors/apps/authentication/views.py @@ -1,6 +1,6 @@ from rest_framework import status from rest_framework.generics import RetrieveUpdateAPIView -from rest_framework.permissions import AllowAny, IsAuthenticated +from rest_framework.permissions import (AllowAny,IsAuthenticated,IsAuthenticatedOrReadOnly) from rest_framework.response import Response from rest_framework.views import APIView from .validations import validate_registration @@ -16,12 +16,12 @@ from django.contrib.sites.shortcuts import get_current_site from rest_framework.generics import GenericAPIView from rest_framework.reverse import reverse - from .renderers import UserJSONRenderer from .serializers import ( LoginSerializer, RegistrationSerializer, UserSerializer, PasswordSerializer ) from .models import User +from authors.apps.articles.models import Article from django.http import JsonResponse diff --git a/authors/apps/profiles/models.py b/authors/apps/profiles/models.py index 4b57983..99a6e3f 100644 --- a/authors/apps/profiles/models.py +++ b/authors/apps/profiles/models.py @@ -2,6 +2,7 @@ from django.dispatch import receiver from django.db.models.signals import post_save from authors.apps.authentication.models import User +from django.contrib.postgres.fields import ArrayField class UserProfile(models.Model): @@ -9,14 +10,13 @@ class UserProfile(models.Model): user = models.OneToOneField(User,on_delete= models.CASCADE) bio = models.TextField(blank=True) fun_fact = models.TextField(blank=True) + time_when_updated = models.DateTimeField(auto_now=True) - - def __str__(self): - return self.user.username - + favorite_article = ArrayField(models.CharField(max_length=100), default=list) + @receiver(post_save, sender=User) def create_user_profile(sender, instance, created, **kwargs): if created: user_profile=UserProfile(user=instance) return user_profile.save() - \ No newline at end of file + \ No newline at end of file diff --git a/authors/apps/profiles/serializers.py b/authors/apps/profiles/serializers.py index e2538cd..79da440 100644 --- a/authors/apps/profiles/serializers.py +++ b/authors/apps/profiles/serializers.py @@ -13,3 +13,9 @@ class UpdateProfileSerializer(serializers.ModelSerializer): class Meta: model = UserProfile fields = ['photo','bio','fun_fact'] + +class FavoriteArticleSerializer(serializers.Serializer): + + class Meta: + model = UserProfile + fields = ['favorite_article'] diff --git a/authors/apps/profiles/tests/__init__.py b/authors/apps/profiles/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/authors/apps/profiles/tests/base_test.py b/authors/apps/profiles/tests/base_test.py new file mode 100644 index 0000000..615cc47 --- /dev/null +++ b/authors/apps/profiles/tests/base_test.py @@ -0,0 +1,74 @@ +from django.urls import path, reverse +from rest_framework import status +from rest_framework.test import APITestCase, APIRequestFactory, APIClient +from authors.apps.authentication.views import RegistrationAPIView, AccountVerified +from authors.apps.authentication.models import User +from authors.apps.authentication.backends import JWTAuthentication + + +class BaseTest(APITestCase): + + def setUp(self): + self.slug = 0 + self.user_data = { + "user": { + "username": "minime", + "email": "alexkayabula@gmail.com", + "password": "W123456/78"}} + self.url = reverse("registration") + self.client = APIClient() + self.client.post(self.url, self.user_data, format='json') + self.request = APIRequestFactory().post( + reverse("registration") + ) + + self.new_article = { + "article": { + "title": "How to fight dragons 8", + "description": "Ever wonder jckvlahvhow?", + "body": "You have kenglto believe" + } + } + + self.article_without_title = { + "article": { + "description": "Ever wonder jckvlahvhow?", + "body": "You have kenglto believe" + } + } + + user = User.objects.get() + request = self.request + token, uid = RegistrationAPIView.generate_token(user, request) + response = self.account_verification(token, uid) + self.assertEqual(response.status_code, status.HTTP_200_OK) + user = User.objects.get() + self.assertTrue(user.is_verified) + + response = self.client.post( + '/api/users/login/', + self.user_data, + format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.login_token = response.data['token'] + self.client.credentials( + HTTP_AUTHORIZATION='Bearer ' + + self.login_token) + + def account_verification(self, token, uid): + request = APIRequestFactory().get( + reverse("verify_account", kwargs={"token": token, "uid": uid}) + ) + verify = AccountVerified.as_view() + response = verify(request, token=token, uid=uid) + return response + + def get_slug(self, response=[]): + self.client.post( + '/api/articles/', + data=self.new_article, + format='json') + response = self.client.get('/api/articles/', format='json') + for i in response.data['results']: + self.slug = i['slug'] + return self.slug \ No newline at end of file diff --git a/authors/apps/profiles/tests/test_favorite_article.py b/authors/apps/profiles/tests/test_favorite_article.py new file mode 100644 index 0000000..52abf69 --- /dev/null +++ b/authors/apps/profiles/tests/test_favorite_article.py @@ -0,0 +1,28 @@ +from .base_test import BaseTest +from rest_framework import status + +class TestfavoriteArticle(BaseTest): + + def test_favorite_article(self): + article = self.client.post('/api/articles/', data = self.new_article,format='json') + slug = self.get_slug( article) + response = self.client.put('/api/users/article/favorite/{}'.format(slug)) + self.assertEqual(response.status_code,status.HTTP_200_OK) + + def test_favorite_article_which_doesnot_exist(self): + response = self.client.put('/api/users/article/favorite/best-43hbc',format='json') + self.assertEqual(response.status_code,status.HTTP_404_NOT_FOUND) + + def test_unfavorite_article(self): + article = self.client.post('/api/articles/', data = self.new_article,format='json') + slug = self.get_slug( article) + self.client.put('/api/users/article/favorite/{}'.format(slug)) + response = self.client.put('/api/users/article/favorite/{}'.format(slug)) + self.assertEqual(response.status_code,status.HTTP_200_OK) + + def test_unfavorite_article_which_doesnot_exist(self): + article = self.client.post('/api/articles/', data = self.new_article,format='json') + slug = self.get_slug( article) + self.client.put('/api/users/article/favorite/{}'.format(slug)) + response = self.client.put('/api/users/article/favorite/man-1628t492') + self.assertEqual(response.status_code,status.HTTP_404_NOT_FOUND) \ No newline at end of file diff --git a/authors/apps/profiles/test_profile.py b/authors/apps/profiles/tests/test_profile.py similarity index 91% rename from authors/apps/profiles/test_profile.py rename to authors/apps/profiles/tests/test_profile.py index 3ad3f8a..31bd64e 100644 --- a/authors/apps/profiles/test_profile.py +++ b/authors/apps/profiles/tests/test_profile.py @@ -2,9 +2,9 @@ from rest_framework import status from django.urls import path, reverse from rest_framework.test import APITestCase, APIRequestFactory, APIClient -from ..authentication.views import RegistrationAPIView, AccountVerified -from ..authentication.models import User -from ..authentication.backends import JWTAuthentication +from authors.apps.authentication.views import RegistrationAPIView, AccountVerified +from authors.apps.authentication.models import User +from authors.apps.authentication.backends import JWTAuthentication class TestUserProfile(APITestCase): @@ -55,4 +55,4 @@ def test_update_profile(self): } response = self.client.put('/api/users/profiles/', update_data, format ='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, status.HTTP_200_OK) \ No newline at end of file diff --git a/authors/apps/profiles/urls.py b/authors/apps/profiles/urls.py index 5da4515..415540b 100644 --- a/authors/apps/profiles/urls.py +++ b/authors/apps/profiles/urls.py @@ -1,9 +1,10 @@ from django.urls import path -from .views import UserProfiles,Updateprofile +from .views import UserProfiles,Updateprofile,FavoriteArticle urlpatterns = [ path('users/profiles', UserProfiles.as_view()), - path('users/profiles/', Updateprofile.as_view(),name="profile") + path('users/profiles/', Updateprofile.as_view(),name="profile"), + path('users/article/favorite/',FavoriteArticle.as_view()) ] diff --git a/authors/apps/profiles/views.py b/authors/apps/profiles/views.py index 222ef58..90f9e6b 100644 --- a/authors/apps/profiles/views.py +++ b/authors/apps/profiles/views.py @@ -1,20 +1,22 @@ from django.shortcuts import render -from .serializers import GetUserProfileSerializer, UpdateProfileSerializer -from rest_framework import generics, viewsets +from .serializers import GetUserProfileSerializer, UpdateProfileSerializer, FavoriteArticleSerializer +from rest_framework.generics import (ListAPIView,RetrieveUpdateAPIView,) from rest_framework.response import Response -from rest_framework.permissions import IsAuthenticated +from rest_framework.permissions import IsAuthenticated,IsAuthenticatedOrReadOnly from rest_framework.views import APIView from rest_framework import status from .models import UserProfile +from ..authentication.models import User +from ..articles.models import Article -class UserProfiles(generics.ListAPIView): +class UserProfiles(ListAPIView): permission_classes = (IsAuthenticated,) queryset = UserProfile.objects.all() serializer_class = GetUserProfileSerializer -class Updateprofile(generics.RetrieveUpdateAPIView): +class Updateprofile(RetrieveUpdateAPIView): permission_classes = (IsAuthenticated,) queryset = UserProfile.objects.all() serializer_class = UpdateProfileSerializer @@ -23,4 +25,28 @@ def update(self,request): serializer =self.serializer_class(request.user.userprofile, data=request.data) serializer.is_valid(raise_exception=True) serializer.update(request.user.userprofile, request.data) - return Response(serializer.data) \ No newline at end of file + return Response(serializer.data) + +class FavoriteArticle(RetrieveUpdateAPIView): + permission_classes = (IsAuthenticatedOrReadOnly,) + serializer_class = FavoriteArticleSerializer + queryset = Article.objects.all() + + def update(self,request,slug): + try: + serializer_instance = self.queryset.get(slug=slug) + except Article.DoesNotExist: + return Response('An article with this slug does not exist.',status.HTTP_404_NOT_FOUND) + + userprofile_obj = request.user.userprofile + + if slug in userprofile_obj.favorite_article: + userprofile_obj.favorite_article.remove(slug) + userprofile_obj.save() + return Response("unfavorited!") + + userprofile_obj.favorite_article.append(slug) + userprofile_obj.save(update_fields = ['favorite_article']) + return Response("favorited!") + +