-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(email reset): reset password via email
- create password reset serializer - create html template to send our message - create reset password view - added email variable to settings.py - added reset view to the url - added respective dependencies to requirements.txt [finishes #161254660]
- Loading branch information
Gidraf
committed
Nov 7, 2018
1 parent
ed93434
commit 71431bd
Showing
23 changed files
with
818 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,6 @@ | |
__pycache__/ | ||
*.py[cod] | ||
*$py.class | ||
|
||
# C extensions | ||
*.so | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from django.apps import AppConfig | ||
|
||
|
||
class ArticlesConfig(AppConfig): | ||
name = 'articles' |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
from django.db import models | ||
from django.core.validators import MaxValueValidator, MinValueValidator | ||
|
||
|
||
class Article(models.Model): | ||
""" | ||
This is the article class | ||
holds data for article class | ||
""" | ||
|
||
slug = models.SlugField(max_length=255, unique=True, db_index=True) | ||
title = models.CharField(max_length=255) | ||
description = models.TextField() | ||
body = models.TextField() | ||
author = models.ForeignKey('authentication.User', | ||
related_name='articles', | ||
on_delete=models.CASCADE) | ||
|
||
def __str__(self): | ||
""" | ||
Return article with human readable format | ||
""" | ||
return self.title | ||
|
||
|
||
class RateArticle(models.Model): | ||
""" | ||
This class rate class | ||
holds data for | ||
""" | ||
rater = models.ForeignKey( | ||
"authentication.User", | ||
related_name="ratearticle", | ||
on_delete=models.CASCADE) # link with the user who rated | ||
article = models.ForeignKey( | ||
"articles.Article", | ||
related_name="ratearticle", | ||
on_delete=models.CASCADE) # link with the article being rated | ||
rate = models.IntegerField(null=False, blank=False, | ||
validators=[ | ||
MaxValueValidator(5), | ||
MinValueValidator(1) | ||
]) # rate value column | ||
|
||
def __str__(self): | ||
""" | ||
Return a human readable format | ||
""" | ||
return self.rate |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
from rest_framework import serializers | ||
from .models import RateArticle | ||
|
||
|
||
class RateArticleSerializer(serializers.ModelSerializer): | ||
""" | ||
validate rate article | ||
""" | ||
slug = serializers.SlugField() | ||
rate = serializers.IntegerField() | ||
|
||
def validate(self, data): | ||
rate = data['rate'] | ||
if not rate > 0 or not rate <= 5: | ||
raise serializers.ValidationError( | ||
'invalid rate value should be > 0 or <=5') | ||
|
||
return data | ||
|
||
class Meta: | ||
model = RateArticle | ||
fields = ("slug", "rate") |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{% autoescape off %} | ||
Hi {{ user }}, | ||
Please click on the link to confirm your registration, | ||
{{ link }} | ||
{% endautoescape %} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
from django.urls import reverse | ||
from ...authentication.models import User | ||
from ..models import Article | ||
from rest_framework.test import APIClient | ||
from rest_framework.test import APITestCase | ||
from rest_framework import status | ||
|
||
|
||
class TestRateArticle(APITestCase): | ||
""" class for testing email verification""" | ||
|
||
def setUp(self): | ||
""" | ||
Prepare test environment for each testcase | ||
""" | ||
self.client = APIClient() | ||
self.article = Article() | ||
self.signup_url = reverse('authentication:register') | ||
self.user_details = { | ||
'user': { | ||
'username': 'user1', | ||
'email': 'evajohnson714@gmail.com', | ||
'password': 'somepass12345', | ||
} | ||
} | ||
self.user_details_2 = { | ||
'user': { | ||
'username': 'user2', | ||
'email': 'evajohnson715s@gmail.com', | ||
'password': 'somepass12345', | ||
} | ||
} | ||
resp = self.client.post( | ||
self.signup_url, | ||
self.user_details, | ||
format='json') | ||
res = self.client.post( | ||
self.signup_url, | ||
self.user_details_2, | ||
format='json') | ||
self.token_2 = res.data['token'] | ||
self.token = resp.data['token'] | ||
self.email = "test_user@gmail.com" | ||
self.name = "test" | ||
self.user = User(username=self.name, email=self.email) | ||
self.user.set_password("@Winners11") | ||
self.user.save() | ||
self.user_id = User.objects.get(email=self.email).pk | ||
self.slug = "this-is-a-question" | ||
title = "this is a question" | ||
description = "this is a description" | ||
body = "this is a body" | ||
author = self.user | ||
article = Article( | ||
author=author, | ||
slug=self.slug, | ||
body=body, | ||
title=title, | ||
description=description) | ||
article.save() | ||
self.rate_details = { | ||
"user": { | ||
"slug": self.slug, | ||
"rate": 3 | ||
} | ||
} | ||
self.rate_url = "http://localhost:8000/api/article/"\ | ||
+ self.slug + "/rate/" | ||
self.view_rates_url = "http://localhost:8000/api/article/rate/" | ||
|
||
def test_rate_article_without_token(self): | ||
""" | ||
test whether rate article without token will fail. | ||
""" | ||
response = self.client.post( | ||
self.rate_url, | ||
self.rate_details, | ||
format='json') | ||
self.assertIn( | ||
'Authentication credentials were not provided.', str( | ||
response.data)) | ||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) | ||
|
||
def test_rate_article(self): | ||
""" | ||
test rate article. | ||
""" | ||
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + self.token) | ||
response = self.client.post( | ||
self.rate_url, | ||
self.rate_details, | ||
format='json') | ||
self.assertIn( | ||
'sucessfully rated', str( | ||
response.data)) | ||
self.assertEqual(response.status_code, status.HTTP_200_OK) | ||
|
||
def test_rate_article_not_found(self): | ||
""" | ||
test whether rate article without article will fail. | ||
""" | ||
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + self.token) | ||
self.rate_details["user"]["slug"] = "-ss-dd-dd-ff" | ||
response = self.client.post( | ||
self.rate_url, | ||
self.rate_details, | ||
format='json') | ||
self.assertIn( | ||
'Article not found', str( | ||
response.data)) | ||
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) | ||
|
||
def test_rate_article_invalid_rate(self): | ||
""" | ||
test whether rate article with invalid data will fail. | ||
""" | ||
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + self.token) | ||
self.rate_details["user"]["rate"] = 7 | ||
response = self.client.post( | ||
self.rate_url, | ||
self.rate_details, | ||
format='json') | ||
self.rate_details["user"]["rate"] = 0 | ||
resp = self.client.post( | ||
self.rate_url, | ||
self.rate_details, | ||
format='json') | ||
self.assertIn( | ||
'invalid rate value should be > 0 or <=5', str( | ||
response.data)) | ||
self.assertIn( | ||
'invalid rate value should be > 0 or <=5', str( | ||
resp.data)) | ||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) | ||
|
||
def test_get_rate_article(self): | ||
""" | ||
test whether rate article with token. | ||
""" | ||
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + self.token) | ||
self.client.post( | ||
self.rate_url, | ||
self.rate_details, | ||
format='json') | ||
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + self.token_2) | ||
self.rate_details["user"]['rate'] = 4 | ||
self.client.post( | ||
self.rate_url, | ||
self.rate_details, | ||
format='json') | ||
response = self.client.get( | ||
self.view_rates_url + str(1) + "/", | ||
format='json') | ||
self.assertEqual( | ||
3.5, | ||
response.data["rates"]) | ||
self.assertEqual(response.status_code, status.HTTP_200_OK) | ||
|
||
def test_get_rate_article_not_found(self): | ||
""" | ||
test whether get rates. | ||
""" | ||
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + self.token) | ||
response = self.client.get( | ||
self.view_rates_url + str(2) + "/", | ||
format='json') | ||
self.assertEqual( | ||
0, | ||
response.data["rates"]) | ||
self.assertEqual(204, status.HTTP_204_NO_CONTENT) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
from django.urls import path | ||
from rest_framework_swagger.views import get_swagger_view | ||
from .views import Rate, ArticleRate | ||
|
||
schema_view = get_swagger_view(title="Articles") | ||
|
||
urlpatterns = [ | ||
path( | ||
'article/<str:slug>/rate/', | ||
Rate.as_view(), name="rate"), | ||
path( | ||
'article/rate/<str:pk>/', | ||
ArticleRate.as_view(), name="rate"), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
from rest_framework import status | ||
import jwt | ||
from rest_framework.generics import CreateAPIView | ||
from rest_framework.views import APIView | ||
from rest_framework.permissions import IsAuthenticated | ||
from rest_framework.response import Response | ||
from rest_framework.authentication import get_authorization_header | ||
from rest_framework import serializers | ||
from ..authentication.renderers import UserJSONRenderer | ||
from .models import RateArticle, Article | ||
from ..authentication.models import User | ||
from authors.settings import SECRET_KEY | ||
from .serializers import RateArticleSerializer | ||
|
||
|
||
class ArticleRate(APIView): | ||
""" | ||
rate class article | ||
""" | ||
permission_classes = (IsAuthenticated,) | ||
renderer_classes = (UserJSONRenderer,) | ||
|
||
def get(self, request, **kwargs): | ||
""" | ||
rate user view | ||
""" | ||
pk = kwargs.get("pk") | ||
rate_articles = None | ||
rate = 0 | ||
try: | ||
# article = Article.objects.get(pk=) | ||
rate_articles = RateArticle.objects.filter(article_id=int(pk)) | ||
except Exception as e: | ||
raise serializers.ValidationError( | ||
str(e) | ||
) | ||
for rate_article in rate_articles: | ||
rate += rate_article.rate | ||
rate_value = 0 | ||
if rate: | ||
rate_value = rate / len(rate_articles) | ||
return Response(data={"rates": round(rate_value, 2)}, | ||
status=status.HTTP_200_OK) | ||
|
||
|
||
class Rate(CreateAPIView): | ||
""" | ||
rate class article | ||
""" | ||
permission_classes = (IsAuthenticated,) | ||
renderer_classes = (UserJSONRenderer,) | ||
serializer_class = RateArticleSerializer | ||
|
||
def post(self, request, **kwargs): | ||
""" | ||
rate user view | ||
""" | ||
auth = get_authorization_header(request).split() | ||
data = request.data.get('user', {}) | ||
serializer = self.serializer_class(data=data) | ||
serializer.validate(data=data) | ||
serializer.is_valid(raise_exception=True) | ||
rate_article = None | ||
try: | ||
article = Article.objects.get(slug=data['slug']) | ||
except Exception: | ||
return Response({"response":"Article not found"}, | ||
status=status.HTTP_204_NO_CONTENT) | ||
token = auth[1] | ||
user_id = jwt.decode(token, SECRET_KEY, algorithm='HS256') | ||
rater = None | ||
try: | ||
rater = User.objects.get(pk=user_id['id']) | ||
except Exception as error: | ||
raise serializers.ValidationError( | ||
str(error) | ||
) | ||
if article.author == rater: | ||
return Response(data={"response": "you cannot rate this article"}, | ||
status=status.HTTP_403_FORBIDDEN) | ||
try: | ||
rate_article = RateArticle.objects.get( | ||
rater=rater, article=article) | ||
rate_article.rate = data["rate"] | ||
rate_article.save() | ||
return Response({"response": "sucessfully rated"}, | ||
status=status.HTTP_200_OK) | ||
except Exception: | ||
rate_article = RateArticle( | ||
rater=rater, article=article, rate=data["rate"]) | ||
rate_article.save() | ||
return Response(data={"response": "sucessfully rated"}, | ||
status=status.HTTP_200_OK) |
Oops, something went wrong.