0.2.3 #99

Merged
sonny merged 112 commits from development into master 2020-05-23 16:58:42 +02:00
61 changed files with 1311 additions and 21 deletions
Showing only changes of commit b2829716b0 - Show all commits

View file

@ -13,6 +13,9 @@ export const CORE_DIR = path.join(PROJECT_DIR, 'news', 'core', 'static', 'core')
const taskMappings = [ const taskMappings = [
{ name: 'login', destDir: `${ACCOUNTS_DIR}/${STATIC_SUFFIX}` }, { name: 'login', destDir: `${ACCOUNTS_DIR}/${STATIC_SUFFIX}` },
{ name: 'register', destDir: `${ACCOUNTS_DIR}/${STATIC_SUFFIX}` },
{ name: 'activate', destDir: `${ACCOUNTS_DIR}/${STATIC_SUFFIX}` },
{ name: 'password-reset', destDir: `${ACCOUNTS_DIR}/${STATIC_SUFFIX}` },
{ name: 'homepage', destDir: `${CORE_DIR}/${STATIC_SUFFIX}` }, { name: 'homepage', destDir: `${CORE_DIR}/${STATIC_SUFFIX}` },
{ name: 'categories', destDir: `${CORE_DIR}/${STATIC_SUFFIX}` }, { name: 'categories', destDir: `${CORE_DIR}/${STATIC_SUFFIX}` },
{ name: 'category', destDir: `${CORE_DIR}/${STATIC_SUFFIX}` }, { name: 'category', destDir: `${CORE_DIR}/${STATIC_SUFFIX}` },

View file

@ -7,6 +7,7 @@ Django==2.2
django-celery-beat==1.5.0 django-celery-beat==1.5.0
djangorestframework==3.9.4 djangorestframework==3.9.4
django-rest-swagger==2.2.0 django-rest-swagger==2.2.0
django-registration-redux==2.6
lxml==4.3.4 lxml==4.3.4
feedparser==5.2.1 feedparser==5.2.1
idna==2.8 idna==2.8

View file

@ -10,14 +10,18 @@
<main class="main"> <main class="main">
<form class="login-form" method="POST" action="{% url 'accounts:login' %}"> <form class="login-form" method="POST" action="{% url 'accounts:login' %}">
{% csrf_token %} {% csrf_token %}
<h4>Login</h4> <div class="form__header">
<h1 class="form__title">Login</h1>
</div>
<fieldset class="login-form__fieldset"> <fieldset class="login-form__fieldset">
{{ form }} {{ form }}
</fieldset> </fieldset>
<fieldset class="login-form__fieldset"> <fieldset class="login-form__fieldset">
<button class="button button--confirm" type="submit">Login</button> <button class="button button--confirm" type="submit">Login</button>
<a><small>I forgot my password</small></a> <a class="link" href="{% url 'accounts:password-reset' %}">
<small class="small">I forgot my password</small>
</a>
</fieldset> </fieldset>
</form> </form>
</main> </main>

View file

@ -1,8 +1,20 @@
import hashlib
import string
from django.utils.crypto import get_random_string
import factory import factory
from registration.models import RegistrationProfile
from newsreader.accounts.models import User from newsreader.accounts.models import User
def get_activation_key():
random_string = get_random_string(length=32, allowed_chars=string.printable)
return hashlib.sha1(random_string.encode("utf-8")).hexdigest()
class UserFactory(factory.django.DjangoModelFactory): class UserFactory(factory.django.DjangoModelFactory):
email = factory.Faker("email") email = factory.Faker("email")
password = factory.Faker("password") password = factory.Faker("password")
@ -17,3 +29,11 @@ class UserFactory(factory.django.DjangoModelFactory):
class Meta: class Meta:
model = User model = User
class RegistrationProfileFactory(factory.django.DjangoModelFactory):
user = factory.SubFactory(UserFactory)
activation_key = factory.LazyFunction(get_activation_key)
class Meta:
model = RegistrationProfile

View file

@ -0,0 +1,102 @@
import datetime
from django.conf import settings
from django.core import mail
from django.test import TestCase
from django.test.utils import override_settings
from django.urls import reverse
from django.utils.translation import gettext as _
from registration.models import RegistrationProfile
from newsreader.accounts.models import User
from newsreader.accounts.tests.factories import UserFactory
class ActivationTestCase(TestCase):
def setUp(self):
self.register_url = reverse("accounts:register")
self.register_success_url = reverse("accounts:register-complete")
self.success_url = reverse("accounts:activate-complete")
def test_activation(self):
data = {
"email": "test@test.com",
"password1": "test12456",
"password2": "test12456",
}
response = self.client.post(self.register_url, data)
self.assertRedirects(response, self.register_success_url)
register_profile = RegistrationProfile.objects.get()
kwargs = {"activation_key": register_profile.activation_key}
response = self.client.get(reverse("accounts:activate", kwargs=kwargs))
self.assertRedirects(response, self.success_url)
def test_expired_key(self):
data = {
"email": "test@test.com",
"password1": "test12456",
"password2": "test12456",
}
response = self.client.post(self.register_url, data)
register_profile = RegistrationProfile.objects.get()
user = register_profile.user
user.date_joined -= datetime.timedelta(days=settings.ACCOUNT_ACTIVATION_DAYS)
user.save()
kwargs = {"activation_key": register_profile.activation_key}
response = self.client.get(reverse("accounts:activate", kwargs=kwargs))
self.assertEqual(200, response.status_code)
self.assertContains(response, _("Account activation failed"))
user.refresh_from_db()
self.assertFalse(user.is_active)
def test_invalid_key(self):
data = {
"email": "test@test.com",
"password1": "test12456",
"password2": "test12456",
}
response = self.client.post(self.register_url, data)
self.assertRedirects(response, self.register_success_url)
kwargs = {"activation_key": "not-a-valid-key"}
response = self.client.get(reverse("accounts:activate", kwargs=kwargs))
self.assertContains(response, _("Account activation failed"))
user = User.objects.get()
self.assertEquals(user.is_active, False)
def test_activated_key(self):
data = {
"email": "test@test.com",
"password1": "test12456",
"password2": "test12456",
}
response = self.client.post(self.register_url, data)
self.assertRedirects(response, self.register_success_url)
register_profile = RegistrationProfile.objects.get()
kwargs = {"activation_key": register_profile.activation_key}
response = self.client.get(reverse("accounts:activate", kwargs=kwargs))
self.assertRedirects(response, self.success_url)
# try this a second time
response = self.client.get(reverse("accounts:activate", kwargs=kwargs))
self.assertRedirects(response, self.success_url)

View file

@ -0,0 +1,164 @@
from typing import Dict
from django.contrib.auth.tokens import default_token_generator as token_generator
from django.core import mail
from django.test import TestCase
from django.urls import reverse
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode
from django.utils.translation import gettext as _
from newsreader.accounts.models import User
from newsreader.accounts.tests.factories import UserFactory
class PasswordResetTestCase(TestCase):
def setUp(self):
self.url = reverse("accounts:password-reset")
self.success_url = reverse("accounts:password-reset-done")
self.user = UserFactory(email="test@test.com")
def test_simple(self):
response = self.client.get(self.url)
self.assertEquals(response.status_code, 200)
def test_password_change(self):
data = {"email": "test@test.com"}
response = self.client.post(self.url, data)
self.assertRedirects(response, self.success_url)
self.assertEquals(len(mail.outbox), 1)
def test_unkown_email(self):
data = {"email": "unknown@test.com"}
response = self.client.post(self.url, data)
self.assertRedirects(response, self.success_url)
self.assertEquals(len(mail.outbox), 0)
def test_repeatedly(self):
data = {"email": "test@test.com"}
response = self.client.post(self.url, data)
self.assertRedirects(response, self.success_url)
response = self.client.post(self.url, data)
self.assertRedirects(response, self.success_url)
self.assertEquals(len(mail.outbox), 2)
class PasswordResetConfirmTestCase(TestCase):
def setUp(self):
self.success_url = reverse("accounts:password-reset-complete")
self.user = UserFactory(email="test@test.com")
def _get_reset_credentials(self) -> Dict:
data = {"email": self.user.email}
response = self.client.post(reverse("accounts:password-reset"), data)
return {
"uidb64": response.context[0]["uid"],
"token": response.context[0]["token"],
}
def test_simple(self):
kwargs = self._get_reset_credentials()
response = self.client.get(
reverse("accounts:password-reset-confirm", kwargs=kwargs)
)
self.assertRedirects(
response, f"/accounts/password-reset/{kwargs['uidb64']}/set-password/"
)
def test_confirm_password(self):
kwargs = self._get_reset_credentials()
response = self.client.get(
reverse("accounts:password-reset-confirm", kwargs=kwargs)
)
data = {"new_password1": "jabbadabadoe", "new_password2": "jabbadabadoe"}
response = self.client.post(response.url, data)
self.assertRedirects(response, self.success_url)
self.user.refresh_from_db()
self.assertTrue(self.user.check_password("jabbadabadoe"))
def test_wrong_uuid(self):
correct_kwargs = self._get_reset_credentials()
wrong_kwargs = {"uidb64": "burp", "token": correct_kwargs["token"]}
response = self.client.get(
reverse("accounts:password-reset-confirm", kwargs=wrong_kwargs)
)
self.assertContains(response, _("Password reset unsuccessful"))
def test_wrong_token(self):
correct_kwargs = self._get_reset_credentials()
wrong_kwargs = {"uidb64": correct_kwargs["uidb64"], "token": "token"}
response = self.client.get(
reverse("accounts:password-reset-confirm", kwargs=wrong_kwargs)
)
self.assertContains(response, _("Password reset unsuccessful"))
def test_wrong_url_args(self):
kwargs = {"uidb64": "burp", "token": "token"}
response = self.client.get(
reverse("accounts:password-reset-confirm", kwargs=kwargs)
)
self.assertContains(response, _("Password reset unsuccessful"))
def test_token_repeatedly(self):
kwargs = self._get_reset_credentials()
response = self.client.get(
reverse("accounts:password-reset-confirm", kwargs=kwargs)
)
data = {"new_password1": "jabbadabadoe", "new_password2": "jabbadabadoe"}
self.client.post(response.url, data)
response = self.client.get(
reverse("accounts:password-reset-confirm", kwargs=kwargs)
)
self.assertContains(response, _("Password reset unsuccessful"))
def test_change_form_repeatedly(self):
kwargs = self._get_reset_credentials()
response = self.client.get(
reverse("accounts:password-reset-confirm", kwargs=kwargs)
)
data = {"new_password1": "new-password", "new_password2": "new-password"}
self.client.post(response.url, data)
data = {"new_password1": "jabbadabadoe", "new_password2": "jabbadabadoe"}
response = self.client.post(
reverse("accounts:password-reset-confirm", kwargs=kwargs)
)
self.assertContains(response, _("Password reset unsuccessful"))
self.user.refresh_from_db()
self.assertTrue(self.user.check_password("new-password"))

View file

@ -0,0 +1,110 @@
from django.core import mail
from django.test import TransactionTestCase as TestCase
from django.test.utils import override_settings
from django.urls import reverse
from django.utils.translation import gettext as _
from registration.models import RegistrationProfile
from newsreader.accounts.models import User
from newsreader.accounts.tests.factories import UserFactory
class RegistrationTestCase(TestCase):
def setUp(self):
self.url = reverse("accounts:register")
self.success_url = reverse("accounts:register-complete")
self.disallowed_url = reverse("accounts:register-closed")
def test_simple(self):
response = self.client.get(self.url)
self.assertEquals(response.status_code, 200)
def test_registration(self):
data = {
"email": "test@test.com",
"password1": "test12456",
"password2": "test12456",
}
response = self.client.post(self.url, data)
self.assertRedirects(response, self.success_url)
self.assertEquals(User.objects.count(), 1)
self.assertEquals(RegistrationProfile.objects.count(), 1)
user = User.objects.get()
self.assertEquals(user.is_active, False)
self.assertEquals(len(mail.outbox), 1)
def test_existing_email(self):
UserFactory(email="test@test.com")
data = {
"email": "test@test.com",
"password1": "test12456",
"password2": "test12456",
}
response = self.client.post(self.url, data)
self.assertEquals(response.status_code, 200)
self.assertEquals(User.objects.count(), 1)
self.assertContains(response, _("User with this Email address already exists"))
def test_pending_registration(self):
data = {
"email": "test@test.com",
"password1": "test12456",
"password2": "test12456",
}
response = self.client.post(self.url, data)
self.assertRedirects(response, self.success_url)
self.assertEquals(User.objects.count(), 1)
self.assertEquals(RegistrationProfile.objects.count(), 1)
user = User.objects.get()
self.assertEquals(user.is_active, False)
self.assertEquals(len(mail.outbox), 1)
response = self.client.post(self.url, data)
self.assertEquals(response.status_code, 200)
self.assertContains(response, _("User with this Email address already exists"))
def test_disabled_account(self):
UserFactory(email="test@test.com", is_active=False)
data = {
"email": "test@test.com",
"password1": "test12456",
"password2": "test12456",
}
response = self.client.post(self.url, data)
self.assertEquals(response.status_code, 200)
self.assertEquals(User.objects.count(), 1)
self.assertContains(response, _("User with this Email address already exists"))
@override_settings(REGISTRATION_OPEN=False)
def test_registration_closed(self):
response = self.client.get(self.url)
self.assertRedirects(response, self.disallowed_url)
data = {
"email": "test@test.com",
"password1": "test12456",
"password2": "test12456",
}
response = self.client.post(self.url, data)
self.assertRedirects(response, self.disallowed_url)
self.assertEquals(User.objects.count(), 0)
self.assertEquals(RegistrationProfile.objects.count(), 0)

View file

@ -0,0 +1,80 @@
from django.conf import settings
from django.core import mail
from django.test import TransactionTestCase as TestCase
from django.test.utils import override_settings
from django.urls import reverse
from django.utils.translation import gettext as _
from registration.models import RegistrationProfile
from newsreader.accounts.models import User
from newsreader.accounts.tests.factories import RegistrationProfileFactory, UserFactory
class ResendActivationTestCase(TestCase):
def setUp(self):
self.url = reverse("accounts:activate-resend")
self.success_url = reverse("accounts:activate-complete")
self.register_url = reverse("accounts:register")
def test_simple(self):
response = self.client.get(self.url)
self.assertEquals(response.status_code, 200)
def test_resent_form(self):
data = {
"email": "test@test.com",
"password1": "test12456",
"password2": "test12456",
}
response = self.client.post(self.register_url, data)
register_profile = RegistrationProfile.objects.get()
original_kwargs = {"activation_key": register_profile.activation_key}
response = self.client.post(self.url, {"email": "test@test.com"})
self.assertContains(response, _("We have sent an email to"))
self.assertEquals(len(mail.outbox), 2)
register_profile.refresh_from_db()
kwargs = {"activation_key": register_profile.activation_key}
response = self.client.get(reverse("accounts:activate", kwargs=kwargs))
self.assertRedirects(response, self.success_url)
register_profile.refresh_from_db()
user = register_profile.user
self.assertEquals(user.is_active, True)
# test the old activation code
response = self.client.get(reverse("accounts:activate", kwargs=original_kwargs))
self.assertEquals(response.status_code, 200)
self.assertContains(response, _("Account activation failed"))
def test_existing_account(self):
user = UserFactory(is_active=True)
profile = RegistrationProfileFactory(user=user, activated=True)
response = self.client.post(self.url, {"email": user.email})
self.assertEquals(response.status_code, 200)
# default behaviour is to show success page but not send an email
self.assertContains(response, _("We have sent an email to"))
self.assertEquals(len(mail.outbox), 0)
def test_no_account(self):
response = self.client.post(self.url, {"email": "fake@mail.com"})
self.assertEquals(response.status_code, 200)
# default behaviour is to show success page but not send an email
self.assertContains(response, _("We have sent an email to"))
self.assertEquals(len(mail.outbox), 0)

View file

@ -1,9 +1,54 @@
from django.urls import include, path from django.urls import include, path
from newsreader.accounts.views import LoginView, LogoutView from newsreader.accounts.views import (
ActivationCompleteView,
ActivationResendView,
ActivationView,
LoginView,
LogoutView,
PasswordResetCompleteView,
PasswordResetConfirmView,
PasswordResetDoneView,
PasswordResetView,
RegistrationClosedView,
RegistrationCompleteView,
RegistrationView,
)
urlpatterns = [ urlpatterns = [
path("login/", LoginView.as_view(), name="login"), path("login/", LoginView.as_view(), name="login"),
path("logout/", LogoutView.as_view(), name="logout"), path("logout/", LogoutView.as_view(), name="logout"),
path("register/", RegistrationView.as_view(), name="register"),
path(
"register/complete/", RegistrationCompleteView.as_view(), name="register-complete"
),
path("register/closed/", RegistrationClosedView.as_view(), name="register-closed"),
path(
"activate/complete/", ActivationCompleteView.as_view(), name="activate-complete"
),
path("activate/resend/", ActivationResendView.as_view(), name="activate-resend"),
path(
# This URL should be placed after all activate/ url's (see arg)
"activate/<str:activation_key>/",
ActivationView.as_view(),
name="activate",
),
path("password-reset/", PasswordResetView.as_view(), name="password-reset"),
path(
"password-reset/done/",
PasswordResetDoneView.as_view(),
name="password-reset-done",
),
path(
"password-reset/<uidb64>/<token>/",
PasswordResetConfirmView.as_view(),
name="password-reset-confirm",
),
path(
"password-reset/done/",
PasswordResetCompleteView.as_view(),
name="password-reset-complete",
),
# TODO: create password change views
] ]

View file

@ -1,14 +1,91 @@
from django.contrib.auth.views import LoginView as DjangoLoginView from django.contrib.auth import views as django_views
from django.contrib.auth.views import LogoutView as DjangoLogoutView from django.shortcuts import render
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.views.generic import TemplateView
from registration.backends.default import views as registration_views
class LoginView(DjangoLoginView): class LoginView(django_views.LoginView):
template_name = "accounts/login.html" template_name = "accounts/login.html"
def get_success_url(self): def get_success_url(self):
return reverse_lazy("index") return reverse_lazy("index")
class LogoutView(DjangoLogoutView): class LogoutView(django_views.LogoutView):
next_page = reverse_lazy("accounts:login") next_page = reverse_lazy("accounts:login")
# RegistrationView shows a registration form and sends the email
# RegistrationCompleteView shows after filling in the registration form
# ActivationView is send within the activation email and activates the account
# ActivationCompleteView shows the success screen when activation was succesful
# ActivationResendView can be used when activation links are expired
# RegistrationClosedView shows when registration is disabled
class RegistrationView(registration_views.RegistrationView):
disallowed_url = reverse_lazy("accounts:register-closed")
template_name = "registration/registration_form.html"
success_url = reverse_lazy("accounts:register-complete")
class RegistrationCompleteView(TemplateView):
template_name = "registration/registration_complete.html"
class RegistrationClosedView(TemplateView):
template_name = "registration/registration_closed.html"
# Redirects or renders failed activation template
class ActivationView(registration_views.ActivationView):
template_name = "registration/activation_failure.html"
def get_success_url(self, user):
return ("accounts:activate-complete", (), {})
class ActivationCompleteView(TemplateView):
template_name = "registration/activation_complete.html"
# Renders activation form resend or resend_activation_complete
class ActivationResendView(registration_views.ResendActivationView):
template_name = "registration/activation_resend_form.html"
def render_form_submitted_template(self, form):
"""
Renders resend activation complete template with the submitted email.
"""
email = form.cleaned_data["email"]
context = {"email": email}
return render(
self.request, "registration/activation_resend_complete.html", context
)
# PasswordResetView sends the mail
# PasswordResetDoneView shows a success message for the above
# PasswordResetConfirmView checks the link the user clicked and
# prompts for a new password
# PasswordResetCompleteView shows a success message for the above
class PasswordResetView(django_views.PasswordResetView):
template_name = "password-reset/password_reset_form.html"
subject_template_name = "password-reset/password_reset_subject.txt"
email_template_name = "password-reset/password_reset_email.html"
success_url = reverse_lazy("accounts:password-reset-done")
class PasswordResetDoneView(django_views.PasswordResetDoneView):
template_name = "password-reset/password_reset_done.html"
class PasswordResetConfirmView(django_views.PasswordResetConfirmView):
template_name = "password-reset/password_reset_confirm.html"
success_url = reverse_lazy("accounts:password-reset-complete")
class PasswordResetCompleteView(django_views.PasswordResetCompleteView):
template_name = "password-reset/password_reset_complete.html"

View file

@ -41,6 +41,7 @@ INSTALLED_APPS = [
"rest_framework_swagger", "rest_framework_swagger",
"celery", "celery",
"django_celery_beat", "django_celery_beat",
"registration",
# app modules # app modules
"newsreader.accounts", "newsreader.accounts",
"newsreader.news.core", "newsreader.news.core",
@ -137,3 +138,7 @@ SWAGGER_SETTINGS = {
"LOGOUT_URL": "rest_framework:logout", "LOGOUT_URL": "rest_framework:logout",
"DOC_EXPANSION": "list", "DOC_EXPANSION": "list",
} }
REGISTRATION_OPEN = True
ACCOUNT_ACTIVATION_DAYS = 7
REGISTRATION_AUTO_LOGIN = True

View file

@ -1,7 +1,7 @@
{% extends "core/category.html" %} {% extends "core/category.html" %}
{% block form-header %} {% block form-header %}
<h1 class="h1">Create a category</h1> <h1 class="h1 form__title">Create a category</h1>
{% endblock %} {% endblock %}
{% block name-input %} {% block name-input %}

View file

@ -1,7 +1,7 @@
{% extends "core/category.html" %} {% extends "core/category.html" %}
{% block form-header %} {% block form-header %}
<h1 class="h1">Update category</h1> <h1 class="h1 form__title">Update category</h1>
{% endblock %} {% endblock %}
{% block name-input %} {% block name-input %}

View file

@ -5,10 +5,15 @@
font-family: $default-font; font-family: $default-font;
color: $default-font-color; color: $default-font-color;
}
body {
@extend .body;
& * { & * {
margin: 0; margin: 0;
padding: 0; padding: 0;
box-sizing: border-box; box-sizing: border-box;
} }
} }

View file

@ -0,0 +1,11 @@
.fieldset {
display: flex;
flex-direction: column;
padding: 15px;
border: none;
}
fieldset {
@extend .fieldset;
}

View file

@ -0,0 +1 @@
@import "fieldset";

View file

@ -8,11 +8,7 @@
background-color: $white; background-color: $white;
&__fieldset { &__fieldset {
display: flex; @extend .fieldset;
flex-direction: column;
padding: 15px;
border: none;
} }
&__header { &__header {
@ -22,7 +18,15 @@
padding: 15px; padding: 15px;
} }
&__title {
font-size: 18px;
}
& .favicon { & .favicon {
height: 30px; height: 30px;
} }
} }
form {
@extend .form;
}

View file

@ -10,3 +10,4 @@
@import "./messages/index"; @import "./messages/index";
@import "./section/index"; @import "./section/index";
@import "./errorlist/index"; @import "./errorlist/index";
@import "./fieldset/index";

View file

@ -14,3 +14,7 @@
} }
} }
} }
ul {
@extend .list;
}

View file

@ -1,3 +0,0 @@
a {
@extend .link;
}

View file

@ -1 +0,0 @@
@import "a";

View file

@ -1,5 +1,9 @@
.help-text { .help-text {
@extends .small; @extend .small;
padding: 5px 15px; padding: 5px 15px;
} }
.helptext {
@extend .help-text;
}

View file

@ -1,6 +1,5 @@
@import "./button/index"; @import "button/index";
@import "link/index"; @import "link/index";
@import "a/index";
@import "h1/index"; @import "h1/index";
@import "h2/index"; @import "h2/index";
@import "h3/index"; @import "h3/index";

View file

@ -1,6 +1,7 @@
.input { .input {
padding: 10px; padding: 10px;
background-color: lighten($gainsboro, +4%);
border: 1px $border-gray solid; border: 1px $border-gray solid;
border-radius: 2px; border-radius: 2px;
@ -8,3 +9,7 @@
border: 1px $focus-blue solid; border: 1px $focus-blue solid;
} }
} }
input {
@extend .input;
}

View file

@ -1,3 +1,7 @@
.label { .label {
padding: 10px; padding: 10px;
} }
label {
@extend .label;
}

View file

@ -6,3 +6,7 @@
cursor: pointer; cursor: pointer;
} }
} }
a {
@extend .link;
}

View file

@ -1,3 +1,8 @@
.small { .small {
color: $nickel; color: $nickel;
font-size: small;
}
small {
@extend .small;
} }

View file

@ -0,0 +1,8 @@
// General imports
@import "../../partials/variables";
@import "../../components/index";
@import "../../elements/index";
// Page specific
@import "./components/index";
@import "./elements/index";

View file

@ -0,0 +1,2 @@
@import "password-reset-form/index";
@import "password-reset-confirm-form/index";

View file

@ -0,0 +1,3 @@
.password-reset-confirm-form {
margin: 20px 0;
}

View file

@ -0,0 +1 @@
@import "password-reset-confirm-form";

View file

@ -0,0 +1,18 @@
.password-reset-form {
margin: 20px 0;
&__fieldset:last-child {
display: flex;
flex-direction: row;
justify-content: space-between;
}
& .form__header {
display: flex;
flex-direction: column;
}
& .form__title {
margin: 0 0 5px 0;
}
}

View file

@ -0,0 +1 @@
@import "password-reset-form";

View file

@ -0,0 +1,8 @@
// General imports
@import "../../partials/variables";
@import "../../components/index";
@import "../../elements/index";
// Page specific
@import "./components/index";
@import "./elements/index";

View file

@ -0,0 +1,11 @@
.activation-form {
margin: 10px 0;
& h4 {
padding: 20px 24px 5px 24px;
}
&__fieldset:last-child {
flex-direction: row;
justify-content: space-between;
}
}

View file

@ -0,0 +1 @@
@import "activation-form";

View file

@ -0,0 +1,2 @@
@import "register-form/index";
@import "activation-form/index";

View file

@ -0,0 +1,11 @@
.register-form {
margin: 10px 0;
& h4 {
padding: 20px 24px 5px 24px;
}
&__fieldset:last-child {
flex-direction: row;
justify-content: space-between;
}
}

View file

@ -0,0 +1 @@
@import "register-form";

View file

@ -0,0 +1,8 @@
// General imports
@import "../../partials/variables";
@import "../../components/index";
@import "../../elements/index";
// Page specific
@import "./components/index";
@import "./elements/index";

View file

@ -16,6 +16,7 @@
<li class="nav__item"><a href="{% url 'accounts:logout' %}">Logout</a></li> <li class="nav__item"><a href="{% url 'accounts:logout' %}">Logout</a></li>
{% else %} {% else %}
<li class="nav__item"><a href="{% url 'accounts:login' %}">Login</a></li> <li class="nav__item"><a href="{% url 'accounts:login' %}">Login</a></li>
<li class="nav__item"><a href="{% url 'accounts:register' %}">Register</a></li>
{% endif %} {% endif %}
</ol> </ol>
</nav> </nav>

View file

@ -0,0 +1,28 @@
{% extends "base.html" %}
{% load static i18n %}
{% block title %}{% trans "Password reset complete" %}{% endblock %}
{% block head %}
<link href="{% static 'accounts/dist/css/password-reset.css' %}" rel="stylesheet" />
{% endblock %}
{% block content %}
<main class="content">
<div class="card">
<div class="card__header">
<h1>{% trans "Password reset complete" %}</h1>
</div>
<div class="card__content">
<p>
{% trans "Your password has been reset!" %}
{% blocktrans %}
You may now <a href="{{ login_url }}">log in</a>
{% endblocktrans %}.
</p>
</div>
<div class="card__footer" />
</div>
</main>
{% endblock %}

View file

@ -0,0 +1,59 @@
{% extends "base.html" %}
{% load static i18n %}
{% block meta %}
<!-- NOTE(joshblum): This prevents leaking the password reset token via the
Referer header to any 3rd party apps on the page. -->
<meta name="referrer" content="origin">
{% endblock %}
{% block title %}{% trans "Confirm password reset" %}{% endblock %}
{% block head %}
<link href="{% static 'accounts/dist/css/password-reset.css' %}" rel="stylesheet" />
{% endblock %}
{% block content %}
<main class="content">
{% if validlink %}
<form class="form password-reset-confirm-form" method="POST">
{% csrf_token %}
<div class="form__header">
<h1 class="form__title">
{% trans "Enter your new password below to reset your password:" %}
</h1>
</div>
<fieldset class="fieldset password-reset-form__fieldset">
{{ form }}
</fieldset>
<fieldset class="fieldset password-reset-form__fieldset">
<a class="button button--cancel" href="{% url 'accounts:login' %}">Cancel</a>
<button class="button button--confirm" type="submit">Change password</button>
</fieldset>
</form>
{% else %}
<div class="card">
<div class="card__header">
<h1>{% trans "Password reset unsuccessful" %}</h1>
</div>
<div class="card__content">
<p>
{% url 'accounts:password-reset' as reset_url %}
{% blocktrans %}
Password reset unsuccessful. Please
<a class="link" href="{{ reset_url }}">try again.</a>
{% endblocktrans %}
</p>
</div>
<div class="card__footer" />
</div>
{% endif %}
</main>
{% endblock %}
{# This is used by django.contrib.auth #}

View file

@ -0,0 +1,27 @@
{% extends "base.html" %}
{% load static i18n %}
{% block title %}{% trans "Password reset" %}{% endblock %}
{% block head %}
<link href="{% static 'accounts/dist/css/password-reset.css' %}" rel="stylesheet" />
{% endblock %}
{% block content %}
<main class="content">
<div class="card">
<div class="card__header">
<h1>{% trans "Password reset" %}</h1>
</div>
<div class="card__content">
<p>
{% blocktrans %}
We have sent you an email with a link to reset your password. Please check
your email and click the link to continue.
{% endblocktrans %}
</p>
</div>
<div class="card__footer" />
</div>
</main>
{% endblock %}

View file

@ -0,0 +1,30 @@
{% load i18n %}
{% blocktrans %}Greetings{% endblocktrans %} {% if user.get_full_name %}{{ user.get_full_name }}{% else %}{{ user }}{% endif %},
{% blocktrans %}
You are receiving this email because you (or someone pretending to be you)
requested that your password be reset on the {{ domain }} site. If you do not
wish to reset your password, please ignore this message.
{% endblocktrans %}
{% blocktrans %}
To reset your password, please click the following link, or copy and paste it
into your web browser:
{% endblocktrans %}
<a rel="noreferrer" href="{{ protocol }}://{{ domain }}{% url 'accounts:password-reset-confirm' uid token %}">
{{ protocol }}://{{ domain }}{% url 'accounts:password-reset-confirm' uid token %}
</a>
{% blocktrans %}
Your username, in case you've forgotten:
{% endblocktrans %} {{ user.get_username }}
{% blocktrans %}
Best regards
{% endblocktrans %},
{{ site_name }}
{% blocktrans %}
Management
{% endblocktrans %}

View file

@ -0,0 +1,34 @@
{% extends "base.html" %}
{% load static i18n %}
{% block title %}{% trans "Reset password" %}{% endblock %}
{% block head %}
<link href="{% static 'accounts/dist/css/password-reset.css' %}" rel="stylesheet" />
{% endblock %}
{% block content %}
<main class="content">
<form class="form password-reset-form" method="POST">
{% csrf_token %}
<div class="form__header">
<h1 class="form__title">{% trans "Reset password" %}</h1>
<p>
{% blocktrans %}
Forgot your password? Enter your email in the form below and we'll send you
instructions for creating a new one.
{% endblocktrans %}
</p>
</div>
<fieldset class="fieldset password-reset-form__fieldset">
{{ form }}
</fieldset>
<fieldset class="fieldset password-reset-form__fieldset">
<a class="button button--cancel" href="{% url 'accounts:login' %}">Cancel</a>
<button class="button button--confirm" type="submit">Send reset mail</button>
</fieldset>
</form>
</main>
{% endblock %}

View file

@ -0,0 +1,6 @@
{% load i18n %}{% trans "Password reset on" %} {{ site_name }}
{% comment %}
See the save method in the PasswordChangeForm in django/contrib/auth/forms.py
for the available context data
{% endcomment %}

View file

@ -0,0 +1,35 @@
{% extends "base.html" %}
{% load static i18n %}
{% block title %}{% trans "Account Activated" %}{% endblock %}
{% block head %}
<link href="{% static 'accounts/dist/css/activate.css' %}" rel="stylesheet" />
{% endblock %}
{% comment %}
**registration/activation_complete.html**
Used after successful account activation. This template has no context
variables of its own, and should simply inform the user that their
account is now active.
{% endcomment %}
{% block content %}
<main class="content">
<div class="card">
<div class="card__header">
<h1>{% trans "Account activated" %}</h1>
</div>
<div class="card__content">
<p>
{% trans "Your account is now activated." %}
{% if not user.is_authenticated %}
{% trans "You can log in." %}
{% endif %}
</p>
</div>
<div class="card__footer" />
</div>
</main>
{% endblock %}

View file

@ -0,0 +1,72 @@
{% load i18n %}
<!doctype html>
<html lang="en">
<head>
<title>{{ site.name }} {% trans "registration" %}</title>
</head>
<body>
<p>
{% blocktrans with site_name=site.name %}
You (or someone pretending to be you) have asked to register an account at
{{ site_name }}. If this wasn't you, please ignore this email
and your address will be removed from our records.
{% endblocktrans %}
</p>
<p>
{% blocktrans %}
To activate this account, please click the following link within the next
{{ expiration_days }} days:
{% endblocktrans %}
</p>
<p>
<a href="http://{{site.domain}}{% url 'accounts:activate' activation_key %}">
{{site.domain}}{% url 'accounts:activate' activation_key %}
</a>
</p>
<p>
{% blocktrans with site_name=site.name %}
Sincerely,
{{ site_name }} Management
{% endblocktrans %}
</p>
</body>
</html>
{% comment %}
**registration/activation_email.html**
Used to generate the html body of the activation email. Should display a
link the user can click to activate the account. This template has the
following context:
``activation_key``
The activation key for the new account.
``expiration_days``
The number of days remaining during which the account may be
activated.
``site``
An object representing the site on which the user registered;
depending on whether ``django.contrib.sites`` is installed, this
may be an instance of either ``django.contrib.sites.models.Site``
(if the sites application is installed) or
``django.contrib.sites.requests.RequestSite`` (if not). Consult `the
documentation for the Django sites framework
<http://docs.djangoproject.com/en/dev/ref/contrib/sites/>`_ for
details regarding these objects' interfaces.
``user``
The new user account
``request``
``HttpRequest`` instance for better flexibility.
For example it can be used to compute absolute register URL:
{{ request.scheme }}://{{ request.get_host }}{% url 'registration_activate' activation_key %}
{% endcomment %}

View file

@ -0,0 +1,52 @@
{% load i18n %}
{% blocktrans with site_name=site.name %}
You (or someone pretending to be you) have asked to register an account at
{{ site_name }}. If this wasn't you, please ignore this email
and your address will be removed from our records.
{% endblocktrans %}
{% blocktrans %}
To activate this account, please click the following link within the next
{{ expiration_days }} days:
{% endblocktrans %}
http://{{site.domain}}{% url 'accounts:activate' activation_key %}
{% blocktrans with site_name=site.name %}
Sincerely,
{{ site_name }} Management
{% endblocktrans %}
{% comment %}
**registration/activation_email.txt**
Used to generate the text body of the activation email. Should display a
link the user can click to activate the account. This template has the
following context:
``activation_key``
The activation key for the new account.
``expiration_days``
The number of days remaining during which the account may be
activated.
``site``
An object representing the site on which the user registered;
depending on whether ``django.contrib.sites`` is installed, this
may be an instance of either ``django.contrib.sites.models.Site``
(if the sites application is installed) or
``django.contrib.sites.requests.RequestSite`` (if not). Consult `the
documentation for the Django sites framework
<http://docs.djangoproject.com/en/dev/ref/contrib/sites/>`_ for
details regarding these objects' interfaces.
``user``
The new user account
``request``
``HttpRequest`` instance for better flexibility.
For example it can be used to compute absolute register URL:
{{ request.scheme }}://{{ request.get_host }}{% url 'registration_activate' activation_key %}
{% endcomment %}

View file

@ -0,0 +1,28 @@
{% load i18n %}{% trans "Account activation on" %} {{ site.name }}
{% comment %}
**registration/activation_email_subject.txt**
Used to generate the subject line of the activation email. Because the
subject line of an email must be a single line of text, any output
from this template will be forcibly condensed to a single line before
being used. This template has the following context:
``activation_key``
The activation key for the new account.
``expiration_days``
The number of days remaining during which the account may be
activated.
``site``
An object representing the site on which the user registered;
depending on whether ``django.contrib.sites`` is installed, this
may be an instance of either ``django.contrib.sites.models.Site``
(if the sites application is installed) or
``django.contrib.sites.requests.RequestSite`` (if not). Consult `the
documentation for the Django sites framework
<http://docs.djangoproject.com/en/dev/ref/contrib/sites/>`_ for
details regarding these objects' interfaces.
{% endcomment %}

View file

@ -0,0 +1,31 @@
{% extends "base.html" %}
{% load static i18n %}
{% block title %}{% trans "Activation Failure" %}{% endblock %}
{% block head %}
<link href="{% static 'accounts/dist/css/activate.css' %}" rel="stylesheet" />
{% endblock %}
{% comment %}
**registration/activate.html**
Used if account activation fails. With the default setup, has the following context:
``activation_key``
The activation key used during the activation attempt.
{% endcomment %}
{% block content %}
<main class="content">
<div class="card">
<div class="card__header">
<h1>{% trans "Activation Failure" %}</h1>
</div>
<div class="card__content">
<p>{% trans "Account activation failed." %}</p>
</div>
<div class="card__footer" />
</div>
</main>
{% endblock %}

View file

@ -0,0 +1,35 @@
{% extends "base.html" %}
{% load static i18n %}
{% block title %}{% trans "Account Activation Resent" %}{% endblock %}
{% block head %}
<link href="{% static 'accounts/dist/css/register.css' %}" rel="stylesheet" />
{% endblock %}
{% comment %}
**registration/resend_activation_complete.html**
Used after form for resending account activation is submitted. By default has
the following context:
``email``
The email address submitted in the resend activation form.
{% endcomment %}
{% block content %}
<main class="content">
<div class="card">
<div class="card__header">
<h1>{% trans "Account activation resent" %}</h1>
</div>
<div class="card__content">
<p>
{% blocktrans %}
We have sent an email to {{ email }} with further instructions.
{% endblocktrans %}
</p>
</div>
<div class="card__footer" />
</div>
</main>
{% endblock %}

View file

@ -0,0 +1,39 @@
{% extends "base.html" %}
{% load static i18n %}
{% block title %}{% trans "Resend Activation Email" %}{% endblock %}
{% block head %}
<link href="{% static 'accounts/dist/css/register.css' %}" rel="stylesheet" />
{% endblock %}
{% comment %}
**registration/resend_activation_form.html**
Used to show the form users will fill out to resend the activation email. By
default, has the following context:
``form``
The registration form. This will be an instance of some subclass
of ``django.forms.Form``; consult `Django's forms documentation
<http://docs.djangoproject.com/en/dev/topics/forms/>`_ for
information on how to display this in a template.
{% endcomment %}
{% block content %}
<main class="content">
<form class="form activation-form" method="POST">
{% csrf_token %}
<div class="form__header">
<h1 class="form__title">Resend activation code</h1>
</div>
<fieldset class="fieldset activation-form__fieldset">
{{ form }}
</fieldset>
<fieldset class="fieldset activation-form__fieldset">
<a class="button button--cancel" href="{% url 'accounts:login' %}">Cancel</a>
<button class="button button--confirm" type="submit">Resend code</button>
</fieldset>
</form>
</main>
{% endblock %}

View file

@ -0,0 +1,25 @@
{% extends "base.html" %}
{% load static i18n %}
{% block title %}{% trans "Registration is closed" %}{% endblock %}
{% block head %}
<link href="{% static 'accounts/dist/css/register.css' %}" rel="stylesheet" />
{% endblock %}
{% block content %}
<main class="content">
<div class="card">
<div class="card__header">
<h1>{% trans "Registration is closed" %}</h1>
</div>
<div class="card__content">
<p>
{% trans "Sorry, but registration is closed at this moment. Come back later." %}
</p>
</div>
<div class="card__footer" />
</div>
</main>
{% endblock %}

View file

@ -0,0 +1,33 @@
{% extends "base.html" %}
{% load static i18n %}
{% block title %}{% trans "Activation email sent" %}{% endblock %}
{% block head %}
<link href="{% static 'accounts/dist/css/register.css' %}" rel="stylesheet" />
{% endblock %}
{% comment %}
**registration/registration_complete.html**
Used after successful completion of the registration form. This
template has no context variables of its own, and should simply inform
the user that an email containing account-activation information has
been sent.
{% endcomment %}
{% block content %}
<main class="content">
<div class="card">
<div class="card__header">
<h1>{% trans "Activation email sent" %}</h1>
</div>
<div class="card__content">
<p>
{% trans "Please check your email to complete the registration process." %}
</p>
</div>
<div class="card__footer" />
</div>
</main>
{% endblock %}

View file

@ -0,0 +1,26 @@
{% extends "base.html" %}
{% load static %}
{% block head %}
<link href="{% static 'accounts/dist/css/register.css' %}" rel="stylesheet" />
{% endblock %}
{% block content %}
<main class="content">
<form class="form register-form" method="POST">
{% csrf_token %}
<div class="form__header">
<h1 class="form__title">Register</h1>
</div>
<fieldset class="fieldset register-form__fieldset">
{{ form }}
</fieldset>
<fieldset class="fieldset register-form__fieldset">
<a class="button button--cancel" href="{% url 'accounts:login' %}">Cancel</a>
<button class="button button--confirm" type="submit">Register</button>
</fieldset>
</form>
</main>
{% endblock %}