-
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.
Adds example Django app with tests from hjwp/book-example repo
- Loading branch information
Showing
81 changed files
with
16,879 additions
and
0 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 |
---|---|---|
@@ -0,0 +1,4 @@ | ||
__pycache__ | ||
*.pyc | ||
db.sqlite3 | ||
functional_tests/screendumps |
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,3 @@ | ||
from django.contrib import admin | ||
|
||
# Register your models here. |
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 AccountsConfig(AppConfig): | ||
name = 'accounts' |
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,20 @@ | ||
from accounts.models import User, Token | ||
|
||
class PasswordlessAuthenticationBackend(object): | ||
|
||
def authenticate(self, uid): | ||
try: | ||
token = Token.objects.get(uid=uid) | ||
return User.objects.get(email=token.email) | ||
except User.DoesNotExist: | ||
return User.objects.create(email=token.email) | ||
except Token.DoesNotExist: | ||
return None | ||
|
||
|
||
def get_user(self, email): | ||
try: | ||
return User.objects.get(email=email) | ||
except User.DoesNotExist: | ||
return None | ||
|
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 @@ | ||
# -*- coding: utf-8 -*- | ||
# Generated by Django 1.10.3 on 2016-12-05 15:02 | ||
from __future__ import unicode_literals | ||
|
||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
initial = True | ||
|
||
dependencies = [ | ||
] | ||
|
||
operations = [ | ||
migrations.CreateModel( | ||
name='User', | ||
fields=[ | ||
('email', models.EmailField(max_length=254, primary_key=True, serialize=False)), | ||
], | ||
), | ||
] |
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,24 @@ | ||
# -*- coding: utf-8 -*- | ||
# Generated by Django 1.10.3 on 2016-12-05 15:06 | ||
from __future__ import unicode_literals | ||
|
||
from django.db import migrations, models | ||
import uuid | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
('accounts', '0001_initial'), | ||
] | ||
|
||
operations = [ | ||
migrations.CreateModel( | ||
name='Token', | ||
fields=[ | ||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||
('email', models.EmailField(max_length=254)), | ||
('uid', models.CharField(default=uuid.uuid4, max_length=40)), | ||
], | ||
), | ||
] |
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,22 @@ | ||
import uuid | ||
from django.contrib import auth | ||
from django.db import models | ||
|
||
|
||
auth.signals.user_logged_in.disconnect(auth.models.update_last_login) | ||
|
||
|
||
class User(models.Model): | ||
email = models.EmailField(primary_key=True) | ||
|
||
REQUIRED_FIELDS = [] | ||
USERNAME_FIELD = 'email' | ||
is_anonymous = False | ||
is_authenticated = True | ||
|
||
|
||
|
||
class Token(models.Model): | ||
email = models.EmailField() | ||
uid = models.CharField(default=uuid.uuid4, max_length=40) | ||
|
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.test import TestCase | ||
from django.contrib.auth import get_user_model | ||
from accounts.authentication import PasswordlessAuthenticationBackend | ||
from accounts.models import Token | ||
User = get_user_model() | ||
|
||
|
||
class AuthenticateTest(TestCase): | ||
|
||
def test_returns_None_if_no_such_token(self): | ||
result = PasswordlessAuthenticationBackend().authenticate( | ||
'no-such-token' | ||
) | ||
self.assertIsNone(result) | ||
|
||
|
||
def test_returns_new_user_with_correct_email_if_token_exists(self): | ||
email = '[email protected]' | ||
token = Token.objects.create(email=email) | ||
user = PasswordlessAuthenticationBackend().authenticate(token.uid) | ||
new_user = User.objects.get(email=email) | ||
self.assertEqual(user, new_user) | ||
|
||
|
||
def test_returns_existing_user_with_correct_email_if_token_exists(self): | ||
email = '[email protected]' | ||
existing_user = User.objects.create(email=email) | ||
token = Token.objects.create(email=email) | ||
user = PasswordlessAuthenticationBackend().authenticate(token.uid) | ||
self.assertEqual(user, existing_user) | ||
|
||
|
||
|
||
class GetUserTest(TestCase): | ||
|
||
def test_gets_user_by_email(self): | ||
User.objects.create(email='[email protected]') | ||
desired_user = User.objects.create(email='[email protected]') | ||
found_user = PasswordlessAuthenticationBackend().get_user( | ||
'[email protected]' | ||
) | ||
self.assertEqual(found_user, desired_user) | ||
|
||
|
||
def test_returns_None_if_no_user_with_that_email(self): | ||
self.assertIsNone( | ||
PasswordlessAuthenticationBackend().get_user('[email protected]') | ||
) | ||
|
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,28 @@ | ||
from django.test import TestCase | ||
from django.contrib import auth | ||
from accounts.models import Token | ||
User = auth.get_user_model() | ||
|
||
|
||
class UserModelTest(TestCase): | ||
|
||
def test_user_is_valid_with_email_only(self): | ||
user = User(email='[email protected]') | ||
user.full_clean() # should not raise | ||
|
||
|
||
def test_no_problem_with_auth_login(self): | ||
user = User.objects.create(email='[email protected]') | ||
user.backend = '' | ||
request = self.client.request().wsgi_request | ||
auth.login(request, user) # should not raise | ||
|
||
|
||
|
||
class TokenModelTest(TestCase): | ||
|
||
def test_links_user_with_auto_generated_uid(self): | ||
token1 = Token.objects.create(email='[email protected]') | ||
token2 = Token.objects.create(email='[email protected]') | ||
self.assertNotEqual(token1.uid, token2.uid) | ||
|
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,92 @@ | ||
from django.test import TestCase | ||
from unittest.mock import patch, call | ||
from accounts.models import Token | ||
|
||
|
||
class SendLoginEmailViewTest(TestCase): | ||
|
||
def test_redirects_to_home_page(self): | ||
response = self.client.post('/accounts/send_login_email', data={ | ||
'email': '[email protected]' | ||
}) | ||
self.assertRedirects(response, '/') | ||
|
||
|
||
def test_adds_success_message(self): | ||
response = self.client.post('/accounts/send_login_email', data={ | ||
'email': '[email protected]' | ||
}, follow=True) | ||
|
||
message = list(response.context['messages'])[0] | ||
self.assertEqual( | ||
message.message, | ||
"Check your email, we've sent you a link you can use to log in." | ||
) | ||
self.assertEqual(message.tags, "success") | ||
|
||
|
||
@patch('accounts.views.send_mail') | ||
def test_sends_mail_to_address_from_post(self, mock_send_mail): | ||
self.client.post('/accounts/send_login_email', data={ | ||
'email': '[email protected]' | ||
}) | ||
|
||
self.assertEqual(mock_send_mail.called, True) | ||
(subject, body, from_email, to_list), kwargs = mock_send_mail.call_args | ||
self.assertEqual(subject, 'Your login link for Superlists') | ||
self.assertEqual(from_email, 'noreply@superlists') | ||
self.assertEqual(to_list, ['[email protected]']) | ||
|
||
|
||
def test_creates_token_associated_with_email(self): | ||
self.client.post('/accounts/send_login_email', data={ | ||
'email': '[email protected]' | ||
}) | ||
token = Token.objects.first() | ||
self.assertEqual(token.email, '[email protected]') | ||
|
||
|
||
@patch('accounts.views.send_mail') | ||
def test_sends_link_to_login_using_token_uid(self, mock_send_mail): | ||
self.client.post('/accounts/send_login_email', data={ | ||
'email': '[email protected]' | ||
}) | ||
|
||
token = Token.objects.first() | ||
expected_url = 'http://testserver/accounts/login?token={uid}'.format( | ||
uid=token.uid | ||
) | ||
(subject, body, from_email, to_list), kwargs = mock_send_mail.call_args | ||
self.assertIn(expected_url, body) | ||
|
||
|
||
|
||
@patch('accounts.views.auth') | ||
class LoginViewTest(TestCase): | ||
|
||
def test_redirects_to_home_page(self, mock_auth): | ||
response = self.client.get('/accounts/login?token=abcd123') | ||
self.assertRedirects(response, '/') | ||
|
||
|
||
def test_calls_authenticate_with_uid_from_get_request(self, mock_auth): | ||
self.client.get('/accounts/login?token=abcd123') | ||
self.assertEqual( | ||
mock_auth.authenticate.call_args, | ||
call(uid='abcd123') | ||
) | ||
|
||
|
||
def test_calls_auth_login_with_user_if_there_is_one(self, mock_auth): | ||
response = self.client.get('/accounts/login?token=abcd123') | ||
self.assertEqual( | ||
mock_auth.login.call_args, | ||
call(response.wsgi_request, mock_auth.authenticate.return_value) | ||
) | ||
|
||
|
||
def test_does_not_login_if_user_is_not_authenticated(self, mock_auth): | ||
mock_auth.authenticate.return_value = None | ||
self.client.get('/accounts/login?token=abcd123') | ||
self.assertEqual(mock_auth.login.called, False) | ||
|
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,9 @@ | ||
from django.conf.urls import url | ||
from accounts import views | ||
from django.contrib.auth.views import logout | ||
|
||
urlpatterns = [ | ||
url(r'^send_login_email$', views.send_login_email, name='send_login_email'), | ||
url(r'^login$', views.login, name='login'), | ||
url(r'^logout$', logout, {'next_page': '/'}, name='logout'), | ||
] |
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,34 @@ | ||
from django.contrib import auth, messages | ||
from django.core.mail import send_mail | ||
from django.core.urlresolvers import reverse | ||
from django.shortcuts import redirect | ||
|
||
from accounts.models import Token | ||
|
||
|
||
def send_login_email(request): | ||
email = request.POST['email'] | ||
token = Token.objects.create(email=email) | ||
url = request.build_absolute_uri( | ||
reverse('login') + '?token={uid}'.format(uid=str(token.uid)) | ||
) | ||
message_body = 'Use this link to log in:\n\n{url}'.format(url=url) | ||
send_mail( | ||
'Your login link for Superlists', | ||
message_body, | ||
'noreply@superlists', | ||
[email] | ||
) | ||
messages.success( | ||
request, | ||
"Check your email, we've sent you a link you can use to log in." | ||
) | ||
return redirect('/') | ||
|
||
|
||
def login(request): | ||
user = auth.authenticate(uid=request.GET.get('token')) | ||
if user: | ||
auth.login(request, user) | ||
return redirect('/') | ||
|
Empty file.
Empty file.
Oops, something went wrong.