Skip to content

Commit

Permalink
feat(email reset): reset password via email
Browse files Browse the repository at this point in the history
  - 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
Show file tree
Hide file tree
Showing 23 changed files with 818 additions and 37 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

Expand Down
5 changes: 4 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ install:
- "pip install coverage"

script:
- python3 manage.py makemigrations
- python3 manage.py makemigrations authentication
- python3 manage.py makemigrations articles
- python3 manage.py migrate authentication
- python3 manage.py migrate articles
- python3 manage.py migrate
- coverage run manage.py test
- coverage report
Expand Down
Empty file.
5 changes: 5 additions & 0 deletions authors/apps/articles/apps.py
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.
49 changes: 49 additions & 0 deletions authors/apps/articles/models.py
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
22 changes: 22 additions & 0 deletions authors/apps/articles/serializers.py
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.
5 changes: 5 additions & 0 deletions authors/apps/articles/tests/templates/activate_account.html
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 %}
170 changes: 170 additions & 0 deletions authors/apps/articles/tests/test_rate_article.py
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)
14 changes: 14 additions & 0 deletions authors/apps/articles/urls.py
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"),
]
93 changes: 93 additions & 0 deletions authors/apps/articles/views.py
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)
Loading

0 comments on commit 71431bd

Please sign in to comment.