Fakultas Ilmu Komputer UI

Commit a584e4b6 authored by Farah Alhaniy Efendi's avatar Farah Alhaniy Efendi
Browse files

Merge branch 'exercise8' into 'master'

Exercise8

See merge request !13
parents 74f53964 cc30e832
Pipeline #25501 passed with stages
in 8 minutes and 43 seconds
from accounts.models import User, Token
class PasswordlessAuthenticationBackend(object):
def authenticate(self, request, 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
\ No newline at end of file
# Generated by Django 2.2.5 on 2019-11-20 12:02
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
dependencies = [
('accounts', '0002_token'),
]
operations = [
migrations.AlterField(
model_name='token',
name='uid',
field=models.CharField(default=uuid.uuid4, max_length=40),
),
]
from django.db import models
import uuid
from django.contrib import auth
auth.signals.user_logged_in.disconnect(auth.models.update_last_login)
class Token(models.Model):
email = models.EmailField()
......
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(
None, 'no-such-token'
)
self.assertIsNone(result)
def test_returns_new_user_with_correct_email_if_token_exists(self):
email = 'edith@example.com'
token = Token.objects.create(email=email)
user = PasswordlessAuthenticationBackend().authenticate(None, 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 = 'edith@example.com'
existing_user = User.objects.create(email=email)
token = Token.objects.create(email=email)
user = PasswordlessAuthenticationBackend().authenticate(None, token.uid)
self.assertEqual(user, existing_user)
class GetUserTest(TestCase):
def test_gets_user_by_email(self):
User.objects.create(email='another@example.com')
desired_user = User.objects.create(email='edith@example.com')
found_user = PasswordlessAuthenticationBackend().get_user(
'edith@example.com'
)
self.assertEqual(found_user, desired_user)
def test_returns_None_if_no_user_with_that_email(self):
self.assertIsNone(
PasswordlessAuthenticationBackend().get_user('edith@example.com')
)
\ No newline at end of file
from django.test import TestCase
from django.contrib.auth import get_user_model
from accounts.models import Token
from django.contrib import auth
User = get_user_model()
......@@ -19,4 +20,10 @@ class UserModelTest(TestCase):
def test_email_is_primary_key(self):
user = User(email='a@b.com')
self.assertEqual(user.pk, 'a@b.com')
\ No newline at end of file
self.assertEqual(user.pk, 'a@b.com')
def test_no_problem_with_auth_login(self):
user = User.objects.create(email='edith@example.com')
user.backend = ''
request = self.client.request().wsgi_request
auth.login(request, user) # should not raise
\ No newline at end of file
from django.test import TestCase
import accounts.views
from unittest.mock import patch, call
from accounts.models import Token
@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)
class SendLoginEmailViewTest(TestCase):
def test_redirects_to_home_page(self):
response = self.client.post('/accounts/send_login_email', data={
'email': 'edith@example.com'
})
self.assertRedirects(response, '/')
def test_sends_mail_to_address_from_post(self):
self.send_mail_called = False
def fake_send_mail(subject, body, from_email, to_list):
self.send_mail_called = True
self.subject = subject
self.body = body
self.from_email = from_email
self.to_list = to_list
accounts.views.send_mail = fake_send_mail
self.client.post('/accounts/send_login_email', data={
'email': 'edith@example.com'
})
self.assertTrue(self.send_mail_called)
self.assertEqual(self.subject, 'Your login link for Superlists')
self.assertEqual(self.from_email, 'noreply@superlists')
self.assertEqual(self.to_list, ['edith@example.com'])
@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': 'edith@example.com'
})
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, ['edith@example.com'])
def test_adds_success_message(self):
response = self.client.post('/accounts/send_login_email', data={
'email': 'edith@example.com'
}, 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")
def test_creates_token_associated_with_email(self):
self.client.post('/accounts/send_login_email', data={
'email': 'edith@example.com'
})
token = Token.objects.first()
self.assertEqual(token.email, 'edith@example.com')
@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': 'edith@example.com'
})
token = Token.objects.first()
expected_url = f'http://testserver/accounts/login?token={token.uid}'
(subject, body, from_email, to_list), kwargs = mock_send_mail.call_args
self.assertIn(expected_url, body)
\ No newline at end of file
from django.conf.urls import url
from accounts import views
from django.contrib.auth.views import LogoutView
urlpatterns = [
url(r'^send_login_email$', views.send_login_email, name='send_login_email'),
url(r'^login$', views.login, name='login'),
url(r'^logout$', LogoutView.as_view(next_page='/'), name='logout'),
]
from django.shortcuts import render
from django.core.mail import send_mail
from django.shortcuts import redirect
from django.contrib import auth, messages
from accounts.models import Token
from django.urls import reverse
# Create your views here.
\ No newline at end of file
def send_login_email(request):
email = request.POST['email']
token = Token.objects.create(email=email)
url = request.build_absolute_uri(
reverse('login') + '?token=' + str(token.uid)
)
message_body = f'Use this link to log in:\n\n{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('/')
\ No newline at end of file
......@@ -10,7 +10,6 @@ SUBJECT = 'Your login link for Superlists'
class LoginTest(FunctionalTest):
@skip
def test_can_get_email_link_to_log_in(self):
# Edith goes to the awesome superlists site
# and notices a "Log in" section in the navbar for the first time
......@@ -40,10 +39,20 @@ class LoginTest(FunctionalTest):
# she clicks it
self.browser.get(url)
# she is logged in!
self.wait_for(
lambda: self.browser.find_element_by_link_text('Log out')
)
navbar = self.browser.find_element_by_css_selector('.navbar')
self.assertIn(TEST_EMAIL, navbar.text)
\ No newline at end of file
self.assertIn(TEST_EMAIL, navbar.text)
# Now she logs out
self.browser.find_element_by_link_text('Log out').click()
# She is logged out
self.wait_for(
lambda: self.browser.find_element_by_name('email')
)
navbar = self.browser.find_element_by_css_selector('.navbar')
self.assertNotIn(TEST_EMAIL, navbar.text)
\ No newline at end of file
......@@ -14,16 +14,39 @@
<div class="container">
<nav class="navbar navbar-default" role="navigation">
<div class="container-fluid">
<a class="navbar-brand" href="/">Superlists</a>
<form class="navbar-form navbar-right" method="POST" action="#">
<div class="container-fluid">
<a class="navbar-brand" href="/">Superlists</a>
{% if user.email %}
<ul class="nav navbar-nav navbar-right">
<li class="navbar-text">Logged in as {{ user.email }}</li>
<li><a href="{% url 'logout' %}">Log out</a></li>
</ul>
{% else %}
<form class="navbar-form navbar-right"
method="POST"
action="{% url 'send_login_email' %}">
<span>Enter email to log in:</span>
<input class="form-control" name="email" type="text" />
{% csrf_token %}
</form>
</div>
</nav>
{% endif %}
</div>
</nav>
{% if messages %}
<div class="row">
<div class="col-md-8">
​{% for message in messages %}
​{% if message.level_tag == 'success' %}
<div class="alert alert-success">{{ message }}</div>
​{% else %}
<div class="alert alert-warning">{{ message }}</div>
​{% endif %}
​{% endfor %}
</div>
</div>
​{% endif %}
<div class="row">
<div class="col-md-6 col-md-offset-3 jumbotron">
<div class="text-center">
......
"""superlists URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/2.1/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.conf.urls import url
from lists import views
......
......@@ -45,6 +45,9 @@ INSTALLED_APPS = [
]
AUTH_USER_MODEL = 'accounts.User'
AUTHENTICATION_BACKENDS = [
'accounts.authentication.PasswordlessAuthenticationBackend',
]
MIDDLEWARE = [
'whitenoise.middleware.WhiteNoiseMiddleware',
......
......@@ -16,8 +16,10 @@ Including another URLconf
from django.conf.urls import include, url
from lists import views as list_views
from lists import urls as list_urls
from accounts import urls as accounts_urls
urlpatterns = [
url(r'^$', list_views.home_page, name='home'),
url(r'^lists/', include(list_urls))
url(r'^lists/', include(list_urls)),
url(r'^accounts/', include(accounts_urls)),
]
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment