Skip to content

Commit

Permalink
Merge 21d67f4 into b8f348f
Browse files Browse the repository at this point in the history
  • Loading branch information
mwinel committed Apr 29, 2019
2 parents b8f348f + 21d67f4 commit 41c326b
Show file tree
Hide file tree
Showing 16 changed files with 295 additions and 36 deletions.
Binary file removed .DS_Store
Binary file not shown.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ var/
*.egg
.idea/
.vscode/*
.DS_Store/*
authors/.DS_Store

# PyInstaller
# Usually these files are written by a python script from a template
Expand All @@ -40,6 +42,7 @@ pip-delete-this-directory.txt
htmlcov/
.tox/
.coverage
.coveragerc
.coverage.*
.cache
nosetests.xml
Expand Down
12 changes: 12 additions & 0 deletions app.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,18 @@
},
"DB": {
"required": true
},
"EMAIL_HOST": {
"required": true
},
"EMAIL_HOST_USER": {
"required": true
},
"EMAIL_HOST_PASSWORD": {
"required": true
},
"EMAIL_PORT": {
"required": true
}
},
"formation": {},
Expand Down
Binary file removed authors/.DS_Store
Binary file not shown.
Binary file removed authors/apps/.DS_Store
Binary file not shown.
2 changes: 1 addition & 1 deletion authors/apps/authentication/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def authenticate_credentials(self, request, token):
except BaseException:
message = "The token provided can not be decoded."
raise exceptions.AuthenticationFailed(message)
user = User.objects.get(email=payload['sub']['email'])
user = User.objects.get(username=payload['sub'])
if not user:
message = "User does not exist in the database."
raise exceptions.AuthenticationFailed(message)
Expand Down
18 changes: 18 additions & 0 deletions authors/apps/authentication/migrations/0002_user_email_verified.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 2.1 on 2019-04-25 07:53

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('authentication', '0001_initial'),
]

operations = [
migrations.AddField(
model_name='user',
name='email_verified',
field=models.BooleanField(default=False),
),
]
44 changes: 24 additions & 20 deletions authors/apps/authentication/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
)
from django.db import models


class UserManager(BaseUserManager):
"""
Django requires that custom users define their own Manager class. By
Expand All @@ -32,21 +33,20 @@ def create_user(self, username, email, password=None):
return user

def create_superuser(self, username, email, password):
"""
Create and return a `User` with superuser powers.
Superuser powers means that this use is an admin that can do anything
they want.
"""
if password is None:
raise TypeError('Superusers must have a password.')
"""
Create and return a `User` with superuser powers.
Superuser powers means that this use is an admin that can do anything
they want.
"""
if password is None:
raise TypeError('Superusers must have a password.')

user = self.create_user(username, email, password)
user.is_superuser = True
user.is_staff = True
user.save()
user = self.create_user(username, email, password)
user.is_superuser = True
user.is_staff = True
user.save()

return user
return user


class User(AbstractBaseUser, PermissionsMixin):
Expand Down Expand Up @@ -80,6 +80,11 @@ class User(AbstractBaseUser, PermissionsMixin):
# A timestamp reprensenting when this object was last updated.
updated_at = models.DateTimeField(auto_now=True)

# The `email_verified` flag represents whether the registered user has a
# verified account.
# The user verifys their account through a link sent to their email.
email_verified = models.BooleanField(default=False)

# More fields required by Django when specifying a custom user model.

# The `USERNAME_FIELD` property tells us which field we will use to log in.
Expand All @@ -94,19 +99,18 @@ class User(AbstractBaseUser, PermissionsMixin):
def __str__(self):
"""
Returns a string representation of this `User`.
This string is used when a `User` is printed in the console.
"""
return self.email

@property
def get_full_name(self):
"""
This method is required by Django for things like handling emails.
Typically, this would be the user's first and last name. Since we do
not store the user's real name, we return their username instead.
"""
return self.username
"""
This method is required by Django for things like handling emails.
Typically, this would be the user's first and last name. Since we do
not store the user's real name, we return their username instead.
"""
return self.username

def get_short_name(self):
"""
Expand Down
1 change: 0 additions & 1 deletion authors/apps/authentication/renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ def render(self, data, media_type=None, renderer_context=None):
# rendering errors.
return super(UserJSONRenderer, self).render(data)


# Finally, we can render our data under the "user" namespace.
return json.dumps({
'user': data
Expand Down
4 changes: 1 addition & 3 deletions authors/apps/authentication/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ class LoginSerializer(serializers.Serializer):
username = serializers.CharField(max_length=255, read_only=True)
password = serializers.CharField(max_length=128, write_only=True)


def validate(self, data):
# The `validate` method is where we make sure that the current
# instance of `LoginSerializer` has "valid". In the case of logging a
Expand Down Expand Up @@ -112,11 +111,10 @@ class Meta:
# specifying the field with `read_only=True` like we did for password
# above. The reason we want to use `read_only_fields` here is because
# we don't need to specify anything else about the field. For the
# password field, we needed to specify the `min_length` and
# password field, we needed to specify the `min_length` and
# `max_length` properties too, but that isn't the case for the token
# field.


def update(self, instance, validated_data):
"""Performs an update on a User."""

Expand Down
Empty file.
141 changes: 141 additions & 0 deletions authors/apps/authentication/tests/test_authentication.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
from django.urls import reverse
from django.contrib.auth import get_user_model
from django.utils.http import urlsafe_base64_encode
from django.utils.encoding import force_bytes

from rest_framework import status
from rest_framework .test import APITestCase, APIClient

from .user_data import UserTestData


class UserManagerTestCase(APITestCase):
"""Usermanager TestCase."""

def setUp(self):
User = get_user_model()
self.client = APIClient()
self.user_test_data = UserTestData()
self.user = User.objects.create_user(
username='test1', email='test1@example.com', password='12345678'
)

def test_create_user(self):
"""Test create user."""
self.assertEqual(self.user.username, 'test1')
self.assertTrue(self.user.is_active)
self.assertFalse(self.user.is_staff)
self.assertFalse(self.user.is_superuser)

def test_user_model_returns_string_object(self):
"""Test user object string representation is returned."""
self.assertTrue(self.user.username, str(self.user))

def test_return_full_name(self):
"""Test return get full name."""
self.assertTrue(self.user.get_full_name)

def test_return_short_name(self):
"""Test return get short name."""
self.assertEqual(self.user.get_short_name(), 'test1')

def test_create_user_with_no_username(self):
"""Test create user with no username."""
User = get_user_model()
with self.assertRaisesMessage(TypeError,
'Users must have a username.'):
User.objects.create_user(
username=None, email='test1@example.com', password='12345678'
)

def test_create_user_with_no_email(self):
"""Test create user with no email."""
User = get_user_model()
with self.assertRaisesMessage(TypeError,
'Users must have an email address.'):
User.objects.create_user(
username='test1', email=None, password='12345678'
)

def test_create_superuser(self):
"""Test create superuser."""
User = get_user_model()
user = User.objects.create_superuser(
username='admin', email='admin@example.com', password='12345678'
)
self.assertEqual(user.username, 'admin')
self.assertTrue(user.is_active)
self.assertTrue(user.is_staff)
self.assertTrue(user.is_superuser)

def test_create_superuser_with_no_password(self):
"""Test create superuser with no password."""
User = get_user_model()
with self.assertRaisesMessage(TypeError,
'Superusers must have a password.'):
User.objects.create_superuser(
username='admin2', email='admin2@example.com', password=None
)

def test_user_signup(self):
"""Test user signup."""
url = reverse('authentication:user_signup')
response = self.client.post(url, self.user_test_data.user_registration,
format="json")
self.assertEqual(response.status_code, status.HTTP_201_CREATED)

def test_user_login(self):
"""Test user login."""
registration_url = reverse('authentication:user_signup')
login_url = reverse('authentication:user_login')
self.client.post(registration_url,
self.user_test_data.user_registration, format="json")
response = self.client.post(login_url, self.user_test_data.user_login,
format="json")
self.assertEqual(response.status_code, status.HTTP_200_OK)

def test_activation_link(self):
"""Test user can get activation link to activate account."""
User = get_user_model()
user2 = User.objects.create_user(
username='test3', email='test3@example.com', password='12345678'
)
uid = user2.username
kwargs = {
"uid": urlsafe_base64_encode(force_bytes(uid)).decode('utf-8')
}
activation_url = reverse('authentication:activation_link',
kwargs=kwargs)
response = self.client.get(activation_url, format="json")
self.assertEqual(response.status_code, status.HTTP_200_OK)

def test_activation_link_invalid(self):
"""Test user registration activation link is invalid."""
User = get_user_model()
user3 = User.objects.create_user(
username='test3', email='test3@example.com', password='12345678'
)
uid = user3.id
kwargs = {
"uid": urlsafe_base64_encode(force_bytes(uid)).decode('utf-8')
}
activation_url = reverse('authentication:activation_link',
kwargs=kwargs)
response = self.client.get(activation_url, format="json")
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertTrue(user3, None)

def test_activation_link_user_not_found(self):
"""Test user registration activation link is invalid."""
User = get_user_model()
user3 = User.objects.create_user(
username='test3', email='test3@example.com', password='12345678'
)
uid = user3.id
kwargs = {
"uid": urlsafe_base64_encode(force_bytes(uid)).decode('utf-8')
}
activation_url = reverse('authentication:activation_link',
kwargs=kwargs)
response = self.client.get(activation_url, format="json")
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
18 changes: 18 additions & 0 deletions authors/apps/authentication/tests/user_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
class UserTestData:
"""User Test Data."""

def __init__(self):
self.user_registration = {
"user": {
"username": "mwinel",
"email": "mwinel@live.com",
"password": "12345678"
}
}

self.user_login = {
"user": {
"email": "mwinel@live.com",
"password": "12345678"
}
}
9 changes: 6 additions & 3 deletions authors/apps/authentication/urls.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
from django.urls import path
from .views import (
LoginAPIView, RegistrationAPIView, UserRetrieveUpdateAPIView
LoginAPIView, RegistrationAPIView, UserRetrieveUpdateAPIView,
AccountActivationAPIView
)

app_name = "authentication"

urlpatterns = [
path('user/', UserRetrieveUpdateAPIView.as_view()),
path('users/', RegistrationAPIView.as_view()),
path('users/login/', LoginAPIView.as_view()),
path('users/', RegistrationAPIView.as_view(), name='user_signup'),
path('users/login/', LoginAPIView.as_view(), name='user_login'),
path('users/<str:uid>/', AccountActivationAPIView.as_view(),
name='activation_link'),
]
Loading

0 comments on commit 41c326b

Please sign in to comment.