From b2829716b0d5add3112bf6ace2aa5a8f963de747 Mon Sep 17 00:00:00 2001 From: sonny Date: Wed, 27 Nov 2019 22:10:02 +0100 Subject: [PATCH] Account management --- gulp/sass.js | 3 + requirements/base.txt | 1 + .../accounts/templates/accounts/login.html | 8 +- src/newsreader/accounts/tests/factories.py | 20 +++ .../accounts/tests/test_activation.py | 102 +++++++++++ .../accounts/tests/test_password_reset.py | 164 ++++++++++++++++++ .../accounts/tests/test_registration.py | 110 ++++++++++++ .../accounts/tests/test_resend_activation.py | 80 +++++++++ src/newsreader/accounts/urls.py | 47 ++++- src/newsreader/accounts/views.py | 85 ++++++++- src/newsreader/conf/base.py | 5 + .../core/templates/core/category-create.html | 2 +- .../core/templates/core/category-update.html | 2 +- .../scss/components/body/_body.scss | 5 + .../scss/components/fieldset/_fieldset.scss | 11 ++ .../scss/components/fieldset/index.scss | 1 + .../scss/components/form/_form.scss | 14 +- src/newsreader/scss/components/index.scss | 1 + .../scss/components/list/_list.scss | 4 + src/newsreader/scss/elements/a/_a.scss | 3 - src/newsreader/scss/elements/a/index.scss | 1 - .../scss/elements/help-text/_help-text.scss | 6 +- src/newsreader/scss/elements/index.scss | 3 +- .../scss/elements/input/_input.scss | 5 + .../scss/elements/label/_label.scss | 4 + src/newsreader/scss/elements/link/_link.scss | 4 + .../scss/elements/small/_small.scss | 5 + .../scss/pages/activate/components/index.scss | 0 .../scss/pages/activate/elements/index.scss | 0 src/newsreader/scss/pages/activate/index.scss | 8 + .../password-reset/components/index.scss | 2 + .../_password-reset-confirm-form.scss | 3 + .../password-reset-confirm-form/index.scss | 1 + .../_password-reset-form.scss | 18 ++ .../components/password-reset-form/index.scss | 1 + .../pages/password-reset/elements/index.scss | 0 .../scss/pages/password-reset/index.scss | 8 + .../activation-form/_activation-form.scss | 11 ++ .../components/activation-form/index.scss | 1 + .../scss/pages/register/components/index.scss | 2 + .../register-form/_register-form.scss | 11 ++ .../components/register-form/index.scss | 1 + .../scss/pages/register/elements/index.scss | 0 src/newsreader/scss/pages/register/index.scss | 8 + src/newsreader/templates/base.html | 1 + .../password_reset_complete.html | 28 +++ .../password_reset_confirm.html | 59 +++++++ .../password-reset/password_reset_done.html | 27 +++ .../password-reset/password_reset_email.html | 30 ++++ .../password-reset/password_reset_form.html | 34 ++++ .../password-reset/password_reset_subject.txt | 6 + .../registration/activation_complete.html | 35 ++++ .../registration/activation_email.html | 72 ++++++++ .../registration/activation_email.txt | 52 ++++++ .../registration/activation_email_subject.txt | 28 +++ .../registration/activation_failure.html | 31 ++++ .../activation_resend_complete.html | 35 ++++ .../registration/activation_resend_form.html | 39 +++++ .../registration/registration_closed.html | 25 +++ .../registration/registration_complete.html | 33 ++++ .../registration/registration_form.html | 26 +++ 61 files changed, 1311 insertions(+), 21 deletions(-) create mode 100644 src/newsreader/accounts/tests/test_activation.py create mode 100644 src/newsreader/accounts/tests/test_password_reset.py create mode 100644 src/newsreader/accounts/tests/test_registration.py create mode 100644 src/newsreader/accounts/tests/test_resend_activation.py create mode 100644 src/newsreader/scss/components/fieldset/_fieldset.scss create mode 100644 src/newsreader/scss/components/fieldset/index.scss delete mode 100644 src/newsreader/scss/elements/a/_a.scss delete mode 100644 src/newsreader/scss/elements/a/index.scss create mode 100644 src/newsreader/scss/pages/activate/components/index.scss create mode 100644 src/newsreader/scss/pages/activate/elements/index.scss create mode 100644 src/newsreader/scss/pages/activate/index.scss create mode 100644 src/newsreader/scss/pages/password-reset/components/index.scss create mode 100644 src/newsreader/scss/pages/password-reset/components/password-reset-confirm-form/_password-reset-confirm-form.scss create mode 100644 src/newsreader/scss/pages/password-reset/components/password-reset-confirm-form/index.scss create mode 100644 src/newsreader/scss/pages/password-reset/components/password-reset-form/_password-reset-form.scss create mode 100644 src/newsreader/scss/pages/password-reset/components/password-reset-form/index.scss create mode 100644 src/newsreader/scss/pages/password-reset/elements/index.scss create mode 100644 src/newsreader/scss/pages/password-reset/index.scss create mode 100644 src/newsreader/scss/pages/register/components/activation-form/_activation-form.scss create mode 100644 src/newsreader/scss/pages/register/components/activation-form/index.scss create mode 100644 src/newsreader/scss/pages/register/components/index.scss create mode 100644 src/newsreader/scss/pages/register/components/register-form/_register-form.scss create mode 100644 src/newsreader/scss/pages/register/components/register-form/index.scss create mode 100644 src/newsreader/scss/pages/register/elements/index.scss create mode 100644 src/newsreader/scss/pages/register/index.scss create mode 100755 src/newsreader/templates/password-reset/password_reset_complete.html create mode 100755 src/newsreader/templates/password-reset/password_reset_confirm.html create mode 100755 src/newsreader/templates/password-reset/password_reset_done.html create mode 100755 src/newsreader/templates/password-reset/password_reset_email.html create mode 100755 src/newsreader/templates/password-reset/password_reset_form.html create mode 100644 src/newsreader/templates/password-reset/password_reset_subject.txt create mode 100755 src/newsreader/templates/registration/activation_complete.html create mode 100644 src/newsreader/templates/registration/activation_email.html create mode 100644 src/newsreader/templates/registration/activation_email.txt create mode 100644 src/newsreader/templates/registration/activation_email_subject.txt create mode 100644 src/newsreader/templates/registration/activation_failure.html create mode 100644 src/newsreader/templates/registration/activation_resend_complete.html create mode 100644 src/newsreader/templates/registration/activation_resend_form.html create mode 100755 src/newsreader/templates/registration/registration_closed.html create mode 100755 src/newsreader/templates/registration/registration_complete.html create mode 100644 src/newsreader/templates/registration/registration_form.html diff --git a/gulp/sass.js b/gulp/sass.js index abe9c53..2a71455 100644 --- a/gulp/sass.js +++ b/gulp/sass.js @@ -13,6 +13,9 @@ export const CORE_DIR = path.join(PROJECT_DIR, 'news', 'core', 'static', 'core') const taskMappings = [ { 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: 'categories', destDir: `${CORE_DIR}/${STATIC_SUFFIX}` }, { name: 'category', destDir: `${CORE_DIR}/${STATIC_SUFFIX}` }, diff --git a/requirements/base.txt b/requirements/base.txt index b3bcaf7..b5a6858 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -7,6 +7,7 @@ Django==2.2 django-celery-beat==1.5.0 djangorestframework==3.9.4 django-rest-swagger==2.2.0 +django-registration-redux==2.6 lxml==4.3.4 feedparser==5.2.1 idna==2.8 diff --git a/src/newsreader/accounts/templates/accounts/login.html b/src/newsreader/accounts/templates/accounts/login.html index f923643..f98a216 100644 --- a/src/newsreader/accounts/templates/accounts/login.html +++ b/src/newsreader/accounts/templates/accounts/login.html @@ -10,14 +10,18 @@
diff --git a/src/newsreader/accounts/tests/factories.py b/src/newsreader/accounts/tests/factories.py index d073c1c..fc13d74 100644 --- a/src/newsreader/accounts/tests/factories.py +++ b/src/newsreader/accounts/tests/factories.py @@ -1,8 +1,20 @@ +import hashlib +import string + +from django.utils.crypto import get_random_string + import factory +from registration.models import RegistrationProfile + 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): email = factory.Faker("email") password = factory.Faker("password") @@ -17,3 +29,11 @@ class UserFactory(factory.django.DjangoModelFactory): class Meta: model = User + + +class RegistrationProfileFactory(factory.django.DjangoModelFactory): + user = factory.SubFactory(UserFactory) + activation_key = factory.LazyFunction(get_activation_key) + + class Meta: + model = RegistrationProfile diff --git a/src/newsreader/accounts/tests/test_activation.py b/src/newsreader/accounts/tests/test_activation.py new file mode 100644 index 0000000..b5a9943 --- /dev/null +++ b/src/newsreader/accounts/tests/test_activation.py @@ -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) diff --git a/src/newsreader/accounts/tests/test_password_reset.py b/src/newsreader/accounts/tests/test_password_reset.py new file mode 100644 index 0000000..1f818c8 --- /dev/null +++ b/src/newsreader/accounts/tests/test_password_reset.py @@ -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")) diff --git a/src/newsreader/accounts/tests/test_registration.py b/src/newsreader/accounts/tests/test_registration.py new file mode 100644 index 0000000..27c87bf --- /dev/null +++ b/src/newsreader/accounts/tests/test_registration.py @@ -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) diff --git a/src/newsreader/accounts/tests/test_resend_activation.py b/src/newsreader/accounts/tests/test_resend_activation.py new file mode 100644 index 0000000..a18df2a --- /dev/null +++ b/src/newsreader/accounts/tests/test_resend_activation.py @@ -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) diff --git a/src/newsreader/accounts/urls.py b/src/newsreader/accounts/urls.py index 61593ed..ac8b2ab 100644 --- a/src/newsreader/accounts/urls.py +++ b/src/newsreader/accounts/urls.py @@ -1,9 +1,54 @@ 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 = [ path("login/", LoginView.as_view(), name="login"), 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//", + 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///", + PasswordResetConfirmView.as_view(), + name="password-reset-confirm", + ), + path( + "password-reset/done/", + PasswordResetCompleteView.as_view(), + name="password-reset-complete", + ), + # TODO: create password change views ] diff --git a/src/newsreader/accounts/views.py b/src/newsreader/accounts/views.py index 4957350..28ae92d 100644 --- a/src/newsreader/accounts/views.py +++ b/src/newsreader/accounts/views.py @@ -1,14 +1,91 @@ -from django.contrib.auth.views import LoginView as DjangoLoginView -from django.contrib.auth.views import LogoutView as DjangoLogoutView +from django.contrib.auth import views as django_views +from django.shortcuts import render 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" def get_success_url(self): return reverse_lazy("index") -class LogoutView(DjangoLogoutView): +class LogoutView(django_views.LogoutView): 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" diff --git a/src/newsreader/conf/base.py b/src/newsreader/conf/base.py index 46bc34a..6f19f91 100644 --- a/src/newsreader/conf/base.py +++ b/src/newsreader/conf/base.py @@ -41,6 +41,7 @@ INSTALLED_APPS = [ "rest_framework_swagger", "celery", "django_celery_beat", + "registration", # app modules "newsreader.accounts", "newsreader.news.core", @@ -137,3 +138,7 @@ SWAGGER_SETTINGS = { "LOGOUT_URL": "rest_framework:logout", "DOC_EXPANSION": "list", } + +REGISTRATION_OPEN = True +ACCOUNT_ACTIVATION_DAYS = 7 +REGISTRATION_AUTO_LOGIN = True diff --git a/src/newsreader/news/core/templates/core/category-create.html b/src/newsreader/news/core/templates/core/category-create.html index 49801bc..50c3a0a 100644 --- a/src/newsreader/news/core/templates/core/category-create.html +++ b/src/newsreader/news/core/templates/core/category-create.html @@ -1,7 +1,7 @@ {% extends "core/category.html" %} {% block form-header %} -

Create a category

+

Create a category

{% endblock %} {% block name-input %} diff --git a/src/newsreader/news/core/templates/core/category-update.html b/src/newsreader/news/core/templates/core/category-update.html index 524d81c..25c4d66 100644 --- a/src/newsreader/news/core/templates/core/category-update.html +++ b/src/newsreader/news/core/templates/core/category-update.html @@ -1,7 +1,7 @@ {% extends "core/category.html" %} {% block form-header %} -

Update category

+

Update category

{% endblock %} {% block name-input %} diff --git a/src/newsreader/scss/components/body/_body.scss b/src/newsreader/scss/components/body/_body.scss index d4316d3..306ad7c 100644 --- a/src/newsreader/scss/components/body/_body.scss +++ b/src/newsreader/scss/components/body/_body.scss @@ -5,10 +5,15 @@ font-family: $default-font; color: $default-font-color; +} + +body { + @extend .body; & * { margin: 0; padding: 0; box-sizing: border-box; } + } diff --git a/src/newsreader/scss/components/fieldset/_fieldset.scss b/src/newsreader/scss/components/fieldset/_fieldset.scss new file mode 100644 index 0000000..c2588b5 --- /dev/null +++ b/src/newsreader/scss/components/fieldset/_fieldset.scss @@ -0,0 +1,11 @@ +.fieldset { + display: flex; + flex-direction: column; + + padding: 15px; + border: none; +} + +fieldset { + @extend .fieldset; +} diff --git a/src/newsreader/scss/components/fieldset/index.scss b/src/newsreader/scss/components/fieldset/index.scss new file mode 100644 index 0000000..be990a8 --- /dev/null +++ b/src/newsreader/scss/components/fieldset/index.scss @@ -0,0 +1 @@ +@import "fieldset"; diff --git a/src/newsreader/scss/components/form/_form.scss b/src/newsreader/scss/components/form/_form.scss index d806a7f..8c51866 100644 --- a/src/newsreader/scss/components/form/_form.scss +++ b/src/newsreader/scss/components/form/_form.scss @@ -8,11 +8,7 @@ background-color: $white; &__fieldset { - display: flex; - flex-direction: column; - - padding: 15px; - border: none; + @extend .fieldset; } &__header { @@ -22,7 +18,15 @@ padding: 15px; } + &__title { + font-size: 18px; + } + & .favicon { height: 30px; } } + +form { + @extend .form; +} diff --git a/src/newsreader/scss/components/index.scss b/src/newsreader/scss/components/index.scss index f52df7a..6a18d4a 100644 --- a/src/newsreader/scss/components/index.scss +++ b/src/newsreader/scss/components/index.scss @@ -10,3 +10,4 @@ @import "./messages/index"; @import "./section/index"; @import "./errorlist/index"; +@import "./fieldset/index"; diff --git a/src/newsreader/scss/components/list/_list.scss b/src/newsreader/scss/components/list/_list.scss index 0e4666c..75e5e94 100644 --- a/src/newsreader/scss/components/list/_list.scss +++ b/src/newsreader/scss/components/list/_list.scss @@ -14,3 +14,7 @@ } } } + +ul { + @extend .list; +} diff --git a/src/newsreader/scss/elements/a/_a.scss b/src/newsreader/scss/elements/a/_a.scss deleted file mode 100644 index 69113c5..0000000 --- a/src/newsreader/scss/elements/a/_a.scss +++ /dev/null @@ -1,3 +0,0 @@ -a { - @extend .link; -} diff --git a/src/newsreader/scss/elements/a/index.scss b/src/newsreader/scss/elements/a/index.scss deleted file mode 100644 index 1bf17d2..0000000 --- a/src/newsreader/scss/elements/a/index.scss +++ /dev/null @@ -1 +0,0 @@ -@import "a"; diff --git a/src/newsreader/scss/elements/help-text/_help-text.scss b/src/newsreader/scss/elements/help-text/_help-text.scss index 6c5fc9b..a90552d 100644 --- a/src/newsreader/scss/elements/help-text/_help-text.scss +++ b/src/newsreader/scss/elements/help-text/_help-text.scss @@ -1,5 +1,9 @@ .help-text { - @extends .small; + @extend .small; padding: 5px 15px; } + +.helptext { + @extend .help-text; +} diff --git a/src/newsreader/scss/elements/index.scss b/src/newsreader/scss/elements/index.scss index cc587d8..46a8bbd 100644 --- a/src/newsreader/scss/elements/index.scss +++ b/src/newsreader/scss/elements/index.scss @@ -1,6 +1,5 @@ -@import "./button/index"; +@import "button/index"; @import "link/index"; -@import "a/index"; @import "h1/index"; @import "h2/index"; @import "h3/index"; diff --git a/src/newsreader/scss/elements/input/_input.scss b/src/newsreader/scss/elements/input/_input.scss index 8cf2340..1cfb4bb 100644 --- a/src/newsreader/scss/elements/input/_input.scss +++ b/src/newsreader/scss/elements/input/_input.scss @@ -1,6 +1,7 @@ .input { padding: 10px; + background-color: lighten($gainsboro, +4%); border: 1px $border-gray solid; border-radius: 2px; @@ -8,3 +9,7 @@ border: 1px $focus-blue solid; } } + +input { + @extend .input; +} diff --git a/src/newsreader/scss/elements/label/_label.scss b/src/newsreader/scss/elements/label/_label.scss index 8c8261c..5030a4c 100644 --- a/src/newsreader/scss/elements/label/_label.scss +++ b/src/newsreader/scss/elements/label/_label.scss @@ -1,3 +1,7 @@ .label { padding: 10px; } + +label { + @extend .label; +} diff --git a/src/newsreader/scss/elements/link/_link.scss b/src/newsreader/scss/elements/link/_link.scss index 96f737f..b485cb3 100644 --- a/src/newsreader/scss/elements/link/_link.scss +++ b/src/newsreader/scss/elements/link/_link.scss @@ -6,3 +6,7 @@ cursor: pointer; } } + +a { + @extend .link; +} diff --git a/src/newsreader/scss/elements/small/_small.scss b/src/newsreader/scss/elements/small/_small.scss index 59830e4..c95bfab 100644 --- a/src/newsreader/scss/elements/small/_small.scss +++ b/src/newsreader/scss/elements/small/_small.scss @@ -1,3 +1,8 @@ .small { color: $nickel; + font-size: small; +} + +small { + @extend .small; } diff --git a/src/newsreader/scss/pages/activate/components/index.scss b/src/newsreader/scss/pages/activate/components/index.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/newsreader/scss/pages/activate/elements/index.scss b/src/newsreader/scss/pages/activate/elements/index.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/newsreader/scss/pages/activate/index.scss b/src/newsreader/scss/pages/activate/index.scss new file mode 100644 index 0000000..16b6493 --- /dev/null +++ b/src/newsreader/scss/pages/activate/index.scss @@ -0,0 +1,8 @@ +// General imports +@import "../../partials/variables"; +@import "../../components/index"; +@import "../../elements/index"; + +// Page specific +@import "./components/index"; +@import "./elements/index"; diff --git a/src/newsreader/scss/pages/password-reset/components/index.scss b/src/newsreader/scss/pages/password-reset/components/index.scss new file mode 100644 index 0000000..4536bb6 --- /dev/null +++ b/src/newsreader/scss/pages/password-reset/components/index.scss @@ -0,0 +1,2 @@ +@import "password-reset-form/index"; +@import "password-reset-confirm-form/index"; diff --git a/src/newsreader/scss/pages/password-reset/components/password-reset-confirm-form/_password-reset-confirm-form.scss b/src/newsreader/scss/pages/password-reset/components/password-reset-confirm-form/_password-reset-confirm-form.scss new file mode 100644 index 0000000..d570c38 --- /dev/null +++ b/src/newsreader/scss/pages/password-reset/components/password-reset-confirm-form/_password-reset-confirm-form.scss @@ -0,0 +1,3 @@ +.password-reset-confirm-form { + margin: 20px 0; +} diff --git a/src/newsreader/scss/pages/password-reset/components/password-reset-confirm-form/index.scss b/src/newsreader/scss/pages/password-reset/components/password-reset-confirm-form/index.scss new file mode 100644 index 0000000..2448efe --- /dev/null +++ b/src/newsreader/scss/pages/password-reset/components/password-reset-confirm-form/index.scss @@ -0,0 +1 @@ +@import "password-reset-confirm-form"; diff --git a/src/newsreader/scss/pages/password-reset/components/password-reset-form/_password-reset-form.scss b/src/newsreader/scss/pages/password-reset/components/password-reset-form/_password-reset-form.scss new file mode 100644 index 0000000..be92ff4 --- /dev/null +++ b/src/newsreader/scss/pages/password-reset/components/password-reset-form/_password-reset-form.scss @@ -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; + } +} diff --git a/src/newsreader/scss/pages/password-reset/components/password-reset-form/index.scss b/src/newsreader/scss/pages/password-reset/components/password-reset-form/index.scss new file mode 100644 index 0000000..1d60faf --- /dev/null +++ b/src/newsreader/scss/pages/password-reset/components/password-reset-form/index.scss @@ -0,0 +1 @@ +@import "password-reset-form"; diff --git a/src/newsreader/scss/pages/password-reset/elements/index.scss b/src/newsreader/scss/pages/password-reset/elements/index.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/newsreader/scss/pages/password-reset/index.scss b/src/newsreader/scss/pages/password-reset/index.scss new file mode 100644 index 0000000..16b6493 --- /dev/null +++ b/src/newsreader/scss/pages/password-reset/index.scss @@ -0,0 +1,8 @@ +// General imports +@import "../../partials/variables"; +@import "../../components/index"; +@import "../../elements/index"; + +// Page specific +@import "./components/index"; +@import "./elements/index"; diff --git a/src/newsreader/scss/pages/register/components/activation-form/_activation-form.scss b/src/newsreader/scss/pages/register/components/activation-form/_activation-form.scss new file mode 100644 index 0000000..39ecc27 --- /dev/null +++ b/src/newsreader/scss/pages/register/components/activation-form/_activation-form.scss @@ -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; + } +} diff --git a/src/newsreader/scss/pages/register/components/activation-form/index.scss b/src/newsreader/scss/pages/register/components/activation-form/index.scss new file mode 100644 index 0000000..748302f --- /dev/null +++ b/src/newsreader/scss/pages/register/components/activation-form/index.scss @@ -0,0 +1 @@ +@import "activation-form"; diff --git a/src/newsreader/scss/pages/register/components/index.scss b/src/newsreader/scss/pages/register/components/index.scss new file mode 100644 index 0000000..3377ff1 --- /dev/null +++ b/src/newsreader/scss/pages/register/components/index.scss @@ -0,0 +1,2 @@ +@import "register-form/index"; +@import "activation-form/index"; diff --git a/src/newsreader/scss/pages/register/components/register-form/_register-form.scss b/src/newsreader/scss/pages/register/components/register-form/_register-form.scss new file mode 100644 index 0000000..e406ae7 --- /dev/null +++ b/src/newsreader/scss/pages/register/components/register-form/_register-form.scss @@ -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; + } +} diff --git a/src/newsreader/scss/pages/register/components/register-form/index.scss b/src/newsreader/scss/pages/register/components/register-form/index.scss new file mode 100644 index 0000000..f0d9b70 --- /dev/null +++ b/src/newsreader/scss/pages/register/components/register-form/index.scss @@ -0,0 +1 @@ +@import "register-form"; diff --git a/src/newsreader/scss/pages/register/elements/index.scss b/src/newsreader/scss/pages/register/elements/index.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/newsreader/scss/pages/register/index.scss b/src/newsreader/scss/pages/register/index.scss new file mode 100644 index 0000000..16b6493 --- /dev/null +++ b/src/newsreader/scss/pages/register/index.scss @@ -0,0 +1,8 @@ +// General imports +@import "../../partials/variables"; +@import "../../components/index"; +@import "../../elements/index"; + +// Page specific +@import "./components/index"; +@import "./elements/index"; diff --git a/src/newsreader/templates/base.html b/src/newsreader/templates/base.html index 7346a66..445d507 100644 --- a/src/newsreader/templates/base.html +++ b/src/newsreader/templates/base.html @@ -16,6 +16,7 @@ {% else %} + {% endif %} diff --git a/src/newsreader/templates/password-reset/password_reset_complete.html b/src/newsreader/templates/password-reset/password_reset_complete.html new file mode 100755 index 0000000..80c2b69 --- /dev/null +++ b/src/newsreader/templates/password-reset/password_reset_complete.html @@ -0,0 +1,28 @@ +{% extends "base.html" %} +{% load static i18n %} + +{% block title %}{% trans "Password reset complete" %}{% endblock %} + +{% block head %} + +{% endblock %} + + +{% block content %} +
+
+
+

{% trans "Password reset complete" %}

+
+
+

+ {% trans "Your password has been reset!" %} + {% blocktrans %} + You may now log in + {% endblocktrans %}. +

+
+ +
+{% endblock %} diff --git a/src/newsreader/templates/password-reset/password_reset_confirm.html b/src/newsreader/templates/password-reset/password_reset_confirm.html new file mode 100755 index 0000000..ff6883f --- /dev/null +++ b/src/newsreader/templates/password-reset/password_reset_confirm.html @@ -0,0 +1,59 @@ +{% extends "base.html" %} +{% load static i18n %} + +{% block meta %} + + +{% endblock %} + +{% block title %}{% trans "Confirm password reset" %}{% endblock %} + +{% block head %} + +{% endblock %} + +{% block content %} +
+ + {% if validlink %} +
+ {% csrf_token %} +
+

+ {% trans "Enter your new password below to reset your password:" %} +

+
+ +
+ {{ form }} +
+
+ Cancel + +
+
+ + {% else %} +
+
+

{% trans "Password reset unsuccessful" %}

+
+
+

+ {% url 'accounts:password-reset' as reset_url %} + {% blocktrans %} + Password reset unsuccessful. Please + try again. + {% endblocktrans %} +

+
+ + {% endif %} + +
+{% endblock %} + + +{# This is used by django.contrib.auth #} diff --git a/src/newsreader/templates/password-reset/password_reset_done.html b/src/newsreader/templates/password-reset/password_reset_done.html new file mode 100755 index 0000000..11a59ff --- /dev/null +++ b/src/newsreader/templates/password-reset/password_reset_done.html @@ -0,0 +1,27 @@ +{% extends "base.html" %} +{% load static i18n %} + +{% block title %}{% trans "Password reset" %}{% endblock %} + +{% block head %} + +{% endblock %} + +{% block content %} +
+
+
+

{% trans "Password reset" %}

+
+
+

+ {% 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 %} +

+
+ +
+{% endblock %} diff --git a/src/newsreader/templates/password-reset/password_reset_email.html b/src/newsreader/templates/password-reset/password_reset_email.html new file mode 100755 index 0000000..69844e8 --- /dev/null +++ b/src/newsreader/templates/password-reset/password_reset_email.html @@ -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 %} + + + {{ protocol }}://{{ domain }}{% url 'accounts:password-reset-confirm' uid token %} + + +{% blocktrans %} + Your username, in case you've forgotten: +{% endblocktrans %} {{ user.get_username }} + +{% blocktrans %} + Best regards +{% endblocktrans %}, +{{ site_name }} +{% blocktrans %} + Management +{% endblocktrans %} diff --git a/src/newsreader/templates/password-reset/password_reset_form.html b/src/newsreader/templates/password-reset/password_reset_form.html new file mode 100755 index 0000000..2ceb647 --- /dev/null +++ b/src/newsreader/templates/password-reset/password_reset_form.html @@ -0,0 +1,34 @@ +{% extends "base.html" %} +{% load static i18n %} + +{% block title %}{% trans "Reset password" %}{% endblock %} + +{% block head %} + +{% endblock %} + +{% block content %} +
+
+ {% csrf_token %} +
+

{% trans "Reset password" %}

+ +

+ {% blocktrans %} + Forgot your password? Enter your email in the form below and we'll send you + instructions for creating a new one. + {% endblocktrans %} +

+
+ +
+ {{ form }} +
+
+ Cancel + +
+
+
+{% endblock %} diff --git a/src/newsreader/templates/password-reset/password_reset_subject.txt b/src/newsreader/templates/password-reset/password_reset_subject.txt new file mode 100644 index 0000000..bbf2b7e --- /dev/null +++ b/src/newsreader/templates/password-reset/password_reset_subject.txt @@ -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 %} diff --git a/src/newsreader/templates/registration/activation_complete.html b/src/newsreader/templates/registration/activation_complete.html new file mode 100755 index 0000000..77561ef --- /dev/null +++ b/src/newsreader/templates/registration/activation_complete.html @@ -0,0 +1,35 @@ +{% extends "base.html" %} +{% load static i18n %} + +{% block title %}{% trans "Account Activated" %}{% endblock %} + +{% block head %} + +{% 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 %} +
+
+
+

{% trans "Account activated" %}

+
+
+

+ {% trans "Your account is now activated." %} + {% if not user.is_authenticated %} + {% trans "You can log in." %} + {% endif %} +

+
+ +
+{% endblock %} diff --git a/src/newsreader/templates/registration/activation_email.html b/src/newsreader/templates/registration/activation_email.html new file mode 100644 index 0000000..8be4421 --- /dev/null +++ b/src/newsreader/templates/registration/activation_email.html @@ -0,0 +1,72 @@ +{% load i18n %} + + + + + {{ site.name }} {% trans "registration" %} + + + +

+ {% 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 %} +

+ +

+ + {{site.domain}}{% url 'accounts:activate' activation_key %} + +

+

+ {% blocktrans with site_name=site.name %} + Sincerely, + {{ site_name }} Management + {% endblocktrans %} +

+ + + + + +{% 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 + `_ 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 %} diff --git a/src/newsreader/templates/registration/activation_email.txt b/src/newsreader/templates/registration/activation_email.txt new file mode 100644 index 0000000..7f52a60 --- /dev/null +++ b/src/newsreader/templates/registration/activation_email.txt @@ -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 + `_ 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 %} diff --git a/src/newsreader/templates/registration/activation_email_subject.txt b/src/newsreader/templates/registration/activation_email_subject.txt new file mode 100644 index 0000000..da0ddeb --- /dev/null +++ b/src/newsreader/templates/registration/activation_email_subject.txt @@ -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 + `_ for + details regarding these objects' interfaces. +{% endcomment %} diff --git a/src/newsreader/templates/registration/activation_failure.html b/src/newsreader/templates/registration/activation_failure.html new file mode 100644 index 0000000..88c9053 --- /dev/null +++ b/src/newsreader/templates/registration/activation_failure.html @@ -0,0 +1,31 @@ +{% extends "base.html" %} +{% load static i18n %} + +{% block title %}{% trans "Activation Failure" %}{% endblock %} + +{% block head %} + +{% 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 %} +
+
+
+

{% trans "Activation Failure" %}

+
+
+

{% trans "Account activation failed." %}

+
+ +
+{% endblock %} diff --git a/src/newsreader/templates/registration/activation_resend_complete.html b/src/newsreader/templates/registration/activation_resend_complete.html new file mode 100644 index 0000000..0b63c89 --- /dev/null +++ b/src/newsreader/templates/registration/activation_resend_complete.html @@ -0,0 +1,35 @@ +{% extends "base.html" %} +{% load static i18n %} + +{% block title %}{% trans "Account Activation Resent" %}{% endblock %} + +{% block head %} + +{% 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 %} +
+
+
+

{% trans "Account activation resent" %}

+
+
+

+ {% blocktrans %} + We have sent an email to {{ email }} with further instructions. + {% endblocktrans %} +

+
+ +
+{% endblock %} diff --git a/src/newsreader/templates/registration/activation_resend_form.html b/src/newsreader/templates/registration/activation_resend_form.html new file mode 100644 index 0000000..e819a1f --- /dev/null +++ b/src/newsreader/templates/registration/activation_resend_form.html @@ -0,0 +1,39 @@ +{% extends "base.html" %} +{% load static i18n %} + +{% block title %}{% trans "Resend Activation Email" %}{% endblock %} + +{% block head %} + +{% 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 + `_ for + information on how to display this in a template. +{% endcomment %} + +{% block content %} +
+
+ {% csrf_token %} +
+

Resend activation code

+
+ +
+ {{ form }} +
+
+ Cancel + +
+
+
+{% endblock %} diff --git a/src/newsreader/templates/registration/registration_closed.html b/src/newsreader/templates/registration/registration_closed.html new file mode 100755 index 0000000..1ac2ad5 --- /dev/null +++ b/src/newsreader/templates/registration/registration_closed.html @@ -0,0 +1,25 @@ +{% extends "base.html" %} +{% load static i18n %} + +{% block title %}{% trans "Registration is closed" %}{% endblock %} + +{% block head %} + +{% endblock %} + + +{% block content %} +
+
+
+

{% trans "Registration is closed" %}

+
+
+

+ {% trans "Sorry, but registration is closed at this moment. Come back later." %} +

+
+ +
+{% endblock %} diff --git a/src/newsreader/templates/registration/registration_complete.html b/src/newsreader/templates/registration/registration_complete.html new file mode 100755 index 0000000..6e508a2 --- /dev/null +++ b/src/newsreader/templates/registration/registration_complete.html @@ -0,0 +1,33 @@ +{% extends "base.html" %} +{% load static i18n %} + +{% block title %}{% trans "Activation email sent" %}{% endblock %} + +{% block head %} + +{% 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 %} +
+
+
+

{% trans "Activation email sent" %}

+
+
+

+ {% trans "Please check your email to complete the registration process." %} +

+
+ +
+{% endblock %} diff --git a/src/newsreader/templates/registration/registration_form.html b/src/newsreader/templates/registration/registration_form.html new file mode 100644 index 0000000..d5e8a13 --- /dev/null +++ b/src/newsreader/templates/registration/registration_form.html @@ -0,0 +1,26 @@ +{% extends "base.html" %} + +{% load static %} + +{% block head %} + +{% endblock %} + +{% block content %} +
+
+ {% csrf_token %} +
+

Register

+
+ +
+ {{ form }} +
+
+ Cancel + +
+
+
+{% endblock %}