Merge pull request #1544 from bookwyrm-social/landing-views

Landing views
This commit is contained in:
Mouse Reeve 2021-10-16 12:17:26 -07:00 committed by GitHub
commit b215316080
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 170 additions and 64 deletions

View file

@ -12,7 +12,7 @@
{% if valid %}
<div>
<form name="register" method="post" action="/register">
<input type=hidden name="invite_code" value="{{ invite.code }}">
<input type="hidden" name="invite_code" value="{{ invite.code }}">
{% include 'snippets/register_form.html' %}
</form>
</div>

View file

@ -14,19 +14,19 @@
{% if show_confirmed_email %}
<p class="notification is-success">{% trans "Success! Email address confirmed." %}</p>
{% endif %}
<form name="login" method="post" action="/login">
<form name="login-confirm" method="post" action="/login">
{% csrf_token %}
{% if show_confirmed_email %}<input type="hidden" name="first_login" value="true">{% endif %}
<div class="field">
<label class="label" for="id_localname">{% trans "Username:" %}</label>
<label class="label" for="id_localname_confirm">{% trans "Username:" %}</label>
<div class="control">
{{ login_form.localname }}
<input type="text" name="localname" maxlength="255" class="input" required="" id="id_localname_confirm" value="{{ login_form.localname.value|default:'' }}">
</div>
</div>
<div class="field">
<label class="label" for="id_password">{% trans "Password:" %}</label>
<label class="label" for="id_password_confirm">{% trans "Password:" %}</label>
<div class="control">
{{ login_form.password }}
<input type="password" name="password" maxlength="128" class="input" required="" id="id_password_confirm">
</div>
{% for error in login_form.password.errors %}
<p class="help is-danger">{{ error | escape }}</p>

View file

@ -14,9 +14,9 @@
<form name="password-reset" method="post" action="/password-reset/{{ code }}">
{% csrf_token %}
<div class="field">
<label class="label" for="id_password">{% trans "Password:" %}</label>
<label class="label" for="id_new_password">{% trans "Password:" %}</label>
<div class="control">
<input type="password" name="password" maxlength="128" class="input" required="" id="id_password">
<input type="password" name="password" maxlength="128" class="input" required="" id="id_new_password">
</div>
</div>
<div class="field">

View file

@ -171,7 +171,7 @@
<input type="text" name="localname" maxlength="150" class="input" required="" id="id_localname" placeholder="{% trans 'username' %}">
</div>
<div class="column">
<label class="is-sr-only" for="id_password">{% trans "Username:" %}</label>
<label class="is-sr-only" for="id_password">{% trans "Password:" %}</label>
<input type="password" name="password" maxlength="128" class="input" required="" id="id_password" placeholder="{% trans 'password' %}">
<p class="help"><a href="{% url 'password-reset' %}">{% trans "Forgot your password?" %}</a></p>
</div>

View file

@ -0,0 +1 @@
from . import *

View file

@ -8,6 +8,7 @@ from django.test.client import RequestFactory
from bookwyrm import forms, models
from bookwyrm import views
from bookwyrm.tests.validate_html import validate_html
class InviteViews(TestCase):
@ -40,7 +41,7 @@ class InviteViews(TestCase):
invite.return_value = True
result = view(request, "hi")
self.assertIsInstance(result, TemplateResponse)
result.render()
validate_html(result.render())
self.assertEqual(result.status_code, 200)
def test_manage_invites(self):
@ -51,7 +52,7 @@ class InviteViews(TestCase):
request.user.is_superuser = True
result = view(request)
self.assertIsInstance(result, TemplateResponse)
result.render()
validate_html(result.render())
self.assertEqual(result.status_code, 200)
def test_manage_invites_post(self):
@ -67,7 +68,7 @@ class InviteViews(TestCase):
result = view(request)
self.assertIsInstance(result, TemplateResponse)
result.render()
validate_html(result.render())
self.assertEqual(result.status_code, 200)
invite = models.SiteInvite.objects.get()
@ -83,7 +84,7 @@ class InviteViews(TestCase):
request = self.factory.post("", form.data)
result = view(request)
result.render()
validate_html(result.render())
req = models.InviteRequest.objects.get()
self.assertEqual(req.email, "new@user.email")
@ -97,7 +98,7 @@ class InviteViews(TestCase):
request = self.factory.post("", form.data)
result = view(request)
result.render()
validate_html(result.render())
# no request created
self.assertFalse(models.InviteRequest.objects.exists())
@ -110,14 +111,14 @@ class InviteViews(TestCase):
request.user.is_superuser = True
result = view(request)
self.assertIsInstance(result, TemplateResponse)
result.render()
validate_html(result.render())
self.assertEqual(result.status_code, 200)
# now with data
models.InviteRequest.objects.create(email="fish@example.com")
result = view(request)
self.assertIsInstance(result, TemplateResponse)
result.render()
validate_html(result.render())
self.assertEqual(result.status_code, 200)
def test_manage_invite_requests_send(self):

View file

@ -7,6 +7,7 @@ from django.test.client import RequestFactory
from bookwyrm import models
from bookwyrm import views
from bookwyrm.tests.validate_html import validate_html
class LandingViews(TestCase):
@ -38,13 +39,13 @@ class LandingViews(TestCase):
with patch("bookwyrm.activitystreams.ActivityStream.get_activity_stream"):
result = view(request)
self.assertEqual(result.status_code, 200)
result.render()
validate_html(result.render())
request.user = self.anonymous_user
result = view(request)
self.assertIsInstance(result, TemplateResponse)
self.assertEqual(result.status_code, 200)
result.render()
validate_html(result.render())
def test_about_page(self):
"""there are so many views, this just makes sure it LOADS"""
@ -53,7 +54,7 @@ class LandingViews(TestCase):
request.user = self.local_user
result = view(request)
self.assertIsInstance(result, TemplateResponse)
result.render()
validate_html(result.render())
self.assertEqual(result.status_code, 200)
def test_landing(self):

View file

@ -7,6 +7,7 @@ from django.test import TestCase
from django.test.client import RequestFactory
from bookwyrm import forms, models, views
from bookwyrm.tests.validate_html import validate_html
# pylint: disable=too-many-public-methods
@ -41,7 +42,7 @@ class LoginViews(TestCase):
result = login(request)
self.assertIsInstance(result, TemplateResponse)
result.render()
validate_html(result.render())
self.assertEqual(result.status_code, 200)
request.user = self.local_user
@ -58,7 +59,7 @@ class LoginViews(TestCase):
request = self.factory.post("", form.data)
request.user = self.anonymous_user
with patch("bookwyrm.views.login.login"):
with patch("bookwyrm.views.landing.login.login"):
result = view(request)
self.assertEqual(result.url, "/")
self.assertEqual(result.status_code, 302)
@ -72,7 +73,7 @@ class LoginViews(TestCase):
request = self.factory.post("", form.data)
request.user = self.anonymous_user
with patch("bookwyrm.views.login.login"):
with patch("bookwyrm.views.landing.login.login"):
result = view(request)
self.assertEqual(result.url, "/")
self.assertEqual(result.status_code, 302)
@ -86,7 +87,7 @@ class LoginViews(TestCase):
request = self.factory.post("", form.data)
request.user = self.anonymous_user
with patch("bookwyrm.views.login.login"):
with patch("bookwyrm.views.landing.login.login"):
result = view(request)
self.assertEqual(result.url, "/")
self.assertEqual(result.status_code, 302)
@ -100,9 +101,9 @@ class LoginViews(TestCase):
request = self.factory.post("", form.data)
request.user = self.anonymous_user
with patch("bookwyrm.views.login.login"):
with patch("bookwyrm.views.landing.login.login"):
result = view(request)
result.render()
validate_html(result.render())
self.assertEqual(result.status_code, 200)
self.assertEqual(
result.context_data["login_form"].non_field_errors,

View file

@ -1,12 +1,16 @@
""" test for app action functionality """
from datetime import timedelta
from unittest.mock import patch
from django.contrib.auth.models import AnonymousUser
from django.core.exceptions import PermissionDenied
from django.template.response import TemplateResponse
from django.test import TestCase
from django.test.client import RequestFactory
from django.utils import timezone
from bookwyrm import models, views
from bookwyrm.tests.validate_html import validate_html
class PasswordViews(TestCase):
@ -37,7 +41,7 @@ class PasswordViews(TestCase):
result = view(request)
self.assertIsInstance(result, TemplateResponse)
result.render()
validate_html(result.render())
self.assertEqual(result.status_code, 200)
def test_password_reset_request_post(self):
@ -47,13 +51,13 @@ class PasswordViews(TestCase):
view = views.PasswordResetRequest.as_view()
resp = view(request)
self.assertEqual(resp.status_code, 200)
resp.render()
validate_html(resp.render())
request = self.factory.post("", {"email": "mouse@mouse.com"})
request.user = self.anonymous_user
with patch("bookwyrm.emailing.send_email.delay"):
resp = view(request)
resp.render()
validate_html(resp.render())
self.assertEqual(models.PasswordReset.objects.get().user, self.local_user)
@ -65,15 +69,43 @@ class PasswordViews(TestCase):
request.user = self.anonymous_user
result = view(request, code.code)
self.assertIsInstance(result, TemplateResponse)
result.render()
validate_html(result.render())
self.assertEqual(result.status_code, 200)
def test_password_reset_nonexistant_code(self):
"""there are so many views, this just makes sure it LOADS"""
view = views.PasswordReset.as_view()
request = self.factory.get("")
request.user = self.anonymous_user
with self.assertRaises(PermissionDenied):
view(request, "beep")
def test_password_reset_invalid_code(self):
"""there are so many views, this just makes sure it LOADS"""
view = views.PasswordReset.as_view()
code = models.PasswordReset.objects.create(
user=self.local_user, expiry=timezone.now() - timedelta(days=2)
)
request = self.factory.get("")
request.user = self.anonymous_user
with self.assertRaises(PermissionDenied):
view(request, code.code)
def test_password_reset_logged_in(self):
"""redirect logged in users"""
view = views.PasswordReset.as_view()
code = models.PasswordReset.objects.create(user=self.local_user)
request = self.factory.get("")
request.user = self.local_user
result = view(request, code.code)
self.assertEqual(result.status_code, 302)
def test_password_reset_post(self):
"""reset from code"""
view = views.PasswordReset.as_view()
code = models.PasswordReset.objects.create(user=self.local_user)
request = self.factory.post("", {"password": "hi", "confirm-password": "hi"})
with patch("bookwyrm.views.password.login"):
with patch("bookwyrm.views.landing.password.login"):
resp = view(request, code.code)
self.assertEqual(resp.status_code, 302)
self.assertFalse(models.PasswordReset.objects.exists())
@ -84,7 +116,7 @@ class PasswordViews(TestCase):
models.PasswordReset.objects.create(user=self.local_user)
request = self.factory.post("", {"password": "hi", "confirm-password": "hi"})
resp = view(request, "jhgdkfjgdf")
resp.render()
validate_html(resp.render())
self.assertTrue(models.PasswordReset.objects.exists())
def test_password_reset_mismatch(self):
@ -93,5 +125,5 @@ class PasswordViews(TestCase):
code = models.PasswordReset.objects.create(user=self.local_user)
request = self.factory.post("", {"password": "hi", "confirm-password": "hihi"})
resp = view(request, code.code)
resp.render()
validate_html(resp.render())
self.assertTrue(models.PasswordReset.objects.exists())

View file

@ -10,6 +10,7 @@ from django.test.client import RequestFactory
from bookwyrm import models, views
from bookwyrm.settings import DOMAIN
from bookwyrm.tests.validate_html import validate_html
# pylint: disable=too-many-public-methods
@ -38,6 +39,13 @@ class RegisterViews(TestCase):
id=1, require_confirm_email=False
)
def test_get_redirect(self, *_):
"""there's no dedicated registration page"""
view = views.Register.as_view()
request = self.factory.get("register/")
response = view(request)
self.assertEqual(response.status_code, 302)
def test_register(self, *_):
"""create a user"""
view = views.Register.as_view()
@ -50,12 +58,12 @@ class RegisterViews(TestCase):
"email": "aa@bb.cccc",
},
)
with patch("bookwyrm.views.register.login"):
with patch("bookwyrm.views.landing.register.login"):
response = view(request)
self.assertEqual(models.User.objects.count(), 2)
self.assertEqual(response.status_code, 302)
nutria = models.User.objects.last()
self.assertEqual(nutria.username, "nutria-user.user_nutria@%s" % DOMAIN)
self.assertEqual(nutria.username, f"nutria-user.user_nutria@{DOMAIN}")
self.assertEqual(nutria.localname, "nutria-user.user_nutria")
self.assertEqual(nutria.local, True)
@ -75,11 +83,11 @@ class RegisterViews(TestCase):
"email": "aa@bb.cccc",
},
)
with patch("bookwyrm.views.register.login"):
with patch("bookwyrm.views.landing.register.login"):
response = view(request)
self.assertEqual(response.status_code, 302)
nutria = models.User.objects.get(localname="nutria")
self.assertEqual(nutria.username, "nutria@%s" % DOMAIN)
self.assertEqual(nutria.username, f"nutria@{DOMAIN}")
self.assertEqual(nutria.local, True)
self.assertFalse(nutria.is_active)
@ -93,12 +101,12 @@ class RegisterViews(TestCase):
"register/",
{"localname": "nutria ", "password": "mouseword", "email": "aa@bb.ccc"},
)
with patch("bookwyrm.views.register.login"):
with patch("bookwyrm.views.landing.register.login"):
response = view(request)
self.assertEqual(models.User.objects.count(), 2)
self.assertEqual(response.status_code, 302)
nutria = models.User.objects.last()
self.assertEqual(nutria.username, "nutria@%s" % DOMAIN)
self.assertEqual(nutria.username, f"nutria@{DOMAIN}")
self.assertEqual(nutria.localname, "nutria")
self.assertEqual(nutria.local, True)
@ -111,7 +119,43 @@ class RegisterViews(TestCase):
)
response = view(request)
self.assertEqual(models.User.objects.count(), 1)
response.render()
validate_html(response.render())
def test_register_error_and_invite(self, *_):
"""redirect to the invite page"""
view = views.Register.as_view()
self.settings.allow_registration = False
self.settings.save()
models.SiteInvite.objects.create(
code="testcode", user=self.local_user, use_limit=1
)
self.assertEqual(models.SiteInvite.objects.get().times_used, 0)
request = self.factory.post(
"register/",
{
"localname": "nutria",
"password": "mouseword",
"email": "",
"invite_code": "testcode",
},
)
with patch("bookwyrm.views.landing.register.login"):
response = view(request)
response = view(request)
validate_html(response.render())
def test_register_username_in_use(self, *_):
"""that username is taken"""
view = views.Register.as_view()
self.assertEqual(models.User.objects.count(), 1)
request = self.factory.post(
"register/",
{"localname": "mouse", "password": "mouseword", "email": "aa@bb.ccc"},
)
response = view(request)
self.assertEqual(models.User.objects.count(), 1)
validate_html(response.render())
def test_register_invalid_username(self, *_):
"""gotta have an email"""
@ -123,7 +167,7 @@ class RegisterViews(TestCase):
)
response = view(request)
self.assertEqual(models.User.objects.count(), 1)
response.render()
validate_html(response.render())
request = self.factory.post(
"register/",
@ -131,7 +175,7 @@ class RegisterViews(TestCase):
)
response = view(request)
self.assertEqual(models.User.objects.count(), 1)
response.render()
validate_html(response.render())
request = self.factory.post(
"register/",
@ -139,7 +183,7 @@ class RegisterViews(TestCase):
)
response = view(request)
self.assertEqual(models.User.objects.count(), 1)
response.render()
validate_html(response.render())
def test_register_closed_instance(self, *_):
"""you can't just register"""
@ -172,7 +216,7 @@ class RegisterViews(TestCase):
"register/",
{"localname": "nutria ", "password": "mouseword", "email": "aa@bleep.com"},
)
with patch("bookwyrm.views.register.login"):
with patch("bookwyrm.views.landing.register.login"):
result = view(request)
self.assertEqual(result.status_code, 302)
self.assertTrue(models.User.objects.filter(email="aa@bleep.com").exists())
@ -196,7 +240,7 @@ class RegisterViews(TestCase):
"invite_code": "testcode",
},
)
with patch("bookwyrm.views.register.login"):
with patch("bookwyrm.views.landing.register.login"):
response = view(request)
self.assertEqual(models.User.objects.count(), 2)
self.assertEqual(response.status_code, 302)
@ -277,7 +321,7 @@ class RegisterViews(TestCase):
result = view(request, "abcde")
self.assertIsInstance(result, TemplateResponse)
result.render()
validate_html(result.render())
self.assertEqual(result.status_code, 200)
self.assertFalse(self.local_user.is_active)
self.assertEqual(self.local_user.deactivation_reason, "pending")
@ -293,10 +337,32 @@ class RegisterViews(TestCase):
result = login(request)
self.assertIsInstance(result, TemplateResponse)
result.render()
validate_html(result.render())
self.assertEqual(result.status_code, 200)
request.user = self.local_user
result = login(request)
self.assertEqual(result.url, "/")
self.assertEqual(result.status_code, 302)
def test_confirm_email_post(self, *_):
"""send the email"""
self.settings.require_confirm_email = True
self.settings.save()
view = views.ConfirmEmail.as_view()
models.SiteInvite.objects.create(
code="testcode", user=self.local_user, use_limit=1
)
request = self.factory.post("", {"code": "testcode"})
request.user = self.anonymous_user
result = view(request)
validate_html(result.render())
def test_resend_link(self, *_):
"""try again"""
request = self.factory.post("", {"email": "mouse@mouse.com"})
request.user = self.anonymous_user
with patch("bookwyrm.emailing.send_email.delay") as mock:
views.resend_link(request)
self.assertEqual(mock.call_count, 1)

View file

@ -32,6 +32,12 @@ from .books.books import Book, upload_cover, add_description, resolve_book
from .books.edit_book import EditBook, ConfirmEditBook
from .books.editions import Editions, switch_edition
# landing
from .landing.landing import About, Home, Landing
from .landing.login import Login, Logout
from .landing.register import Register, ConfirmEmail, ConfirmEmailCode, resend_link
from .landing.password import PasswordResetRequest, PasswordReset
# misc views
from .author import Author, EditAuthor
from .directory import Directory
@ -45,17 +51,13 @@ from .import_data import Import, ImportStatus
from .inbox import Inbox
from .interaction import Favorite, Unfavorite, Boost, Unboost
from .isbn import Isbn
from .landing import About, Home, Landing
from .list import Lists, SavedLists, List, Curate, UserLists
from .list import save_list, unsave_list, delete_list
from .login import Login, Logout
from .notifications import Notifications
from .outbox import Outbox
from .reading import create_readthrough, delete_readthrough, delete_progressupdate
from .reading import ReadingStatus
from .register import Register, ConfirmEmail, ConfirmEmailCode, resend_link
from .rss_feed import RssFeed
from .password import PasswordResetRequest, PasswordReset
from .search import Search
from .shelf import Shelf
from .shelf import create_shelf, delete_shelf

View file

@ -81,7 +81,7 @@ class Invite(View):
"invite": invite,
"valid": invite.valid() if invite else True,
}
return TemplateResponse(request, "invite.html", data)
return TemplateResponse(request, "landing/invite.html", data)
# post handling is in views.register.Register

View file

View file

@ -3,8 +3,8 @@ from django.template.response import TemplateResponse
from django.views import View
from bookwyrm import forms
from .feed import Feed
from . import helpers
from bookwyrm.views import helpers
from bookwyrm.views.feed import Feed
# pylint: disable= no-self-use

View file

@ -29,7 +29,7 @@ class Login(View):
"login_form": forms.LoginForm(),
"register_form": forms.RegisterForm(),
}
return TemplateResponse(request, "login.html", data)
return TemplateResponse(request, "landing/login.html", data)
@sensitive_variables("password")
@method_decorator(sensitive_post_parameters("password"))
@ -69,7 +69,7 @@ class Login(View):
login_form.non_field_errors = _("Username or password are incorrect")
register_form = forms.RegisterForm()
data = {"login_form": login_form, "register_form": register_form}
return TemplateResponse(request, "login.html", data)
return TemplateResponse(request, "landing/login.html", data)
@method_decorator(login_required, name="dispatch")

View file

@ -18,7 +18,7 @@ class PasswordResetRequest(View):
"""password reset page"""
return TemplateResponse(
request,
"password_reset_request.html",
"landing/password_reset_request.html",
)
def post(self, request):
@ -30,7 +30,9 @@ class PasswordResetRequest(View):
)
except models.User.DoesNotExist:
data = {"error": _("No user with that email address was found.")}
return TemplateResponse(request, "password_reset_request.html", data)
return TemplateResponse(
request, "landing/password_reset_request.html", data
)
# remove any existing password reset cods for this user
models.PasswordReset.objects.filter(user=user).all().delete()
@ -39,7 +41,7 @@ class PasswordResetRequest(View):
code = models.PasswordReset.objects.create(user=user)
password_reset_email(code)
data = {"message": _(f"A password reset link was sent to {email}")}
return TemplateResponse(request, "password_reset_request.html", data)
return TemplateResponse(request, "landing/password_reset_request.html", data)
class PasswordReset(View):
@ -56,7 +58,7 @@ class PasswordReset(View):
except models.PasswordReset.DoesNotExist:
raise PermissionDenied()
return TemplateResponse(request, "password_reset.html", {"code": code})
return TemplateResponse(request, "landing/password_reset.html", {"code": code})
def post(self, request, code):
"""allow a user to change their password through an emailed token"""
@ -64,7 +66,7 @@ class PasswordReset(View):
reset_code = models.PasswordReset.objects.get(code=code)
except models.PasswordReset.DoesNotExist:
data = {"errors": ["Invalid password reset link"]}
return TemplateResponse(request, "password_reset.html", data)
return TemplateResponse(request, "landing/password_reset.html", data)
user = reset_code.user
@ -73,7 +75,7 @@ class PasswordReset(View):
if new_password != confirm_password:
data = {"errors": ["Passwords do not match"]}
return TemplateResponse(request, "password_reset.html", data)
return TemplateResponse(request, "landing/password_reset.html", data)
user.set_password(new_password)
user.save(broadcast=False, update_fields=["password"])

View file

@ -65,8 +65,8 @@ class Register(View):
"valid": invite.valid() if invite else True,
}
if invite:
return TemplateResponse(request, "invite.html", data)
return TemplateResponse(request, "login.html", data)
return TemplateResponse(request, "landing/invite.html", data)
return TemplateResponse(request, "landing/login.html", data)
username = f"{localname}@{DOMAIN}"
user = models.User.objects.create_user(

View file

@ -3,7 +3,7 @@ colorthief==0.2.1
Django==3.2.5
django-imagekit==4.0.2
django-model-utils==4.0.0
environs==7.2.0
environs==9.3.4
flower==0.9.4
Markdown==3.3.3
Pillow>=8.2.0