From a84198027441de7c4efc8b4e2b7b9804b7b230f6 Mon Sep 17 00:00:00 2001
From: Dmitriy Lyalyuev <dmitriy@lyalyuev.info>
Date: Tue, 25 Oct 2022 16:44:24 +0300
Subject: [PATCH] feat: email confirmation

---
 later42/settings.py                     |  1 +
 later42/templates/email_activation.html |  5 ++++
 later42/templates/email_sent.html       |  6 +++++
 later42/tokens.py                       | 13 ++++++++++
 later42/urls.py                         |  4 +++-
 later42/views/account_activation.py     | 29 ++++++++++++++++++++++
 later42/views/signup.py                 | 32 +++++++++++++++++++++----
 requirements.txt                        |  1 +
 8 files changed, 85 insertions(+), 6 deletions(-)
 create mode 100644 later42/templates/email_activation.html
 create mode 100644 later42/templates/email_sent.html
 create mode 100644 later42/tokens.py
 create mode 100644 later42/views/account_activation.py

diff --git a/later42/settings.py b/later42/settings.py
index aa31f05..1dd70af 100644
--- a/later42/settings.py
+++ b/later42/settings.py
@@ -160,3 +160,4 @@ REST_FRAMEWORK = {
 
 URLS_PER_PAGE = 20
 READABILITY_HOST = os.getenv('READABILITY_HOST', None)
+EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
diff --git a/later42/templates/email_activation.html b/later42/templates/email_activation.html
new file mode 100644
index 0000000..179e6d0
--- /dev/null
+++ b/later42/templates/email_activation.html
@@ -0,0 +1,5 @@
+{% autoescape off %}
+Привет, {{ user.username }}.
+Ссылка для активации аккаунта:
+http://{{ domain }}{% url 'activate' uidb64=uid token=token %}
+{% endautoescape %}
\ No newline at end of file
diff --git a/later42/templates/email_sent.html b/later42/templates/email_sent.html
new file mode 100644
index 0000000..884af1b
--- /dev/null
+++ b/later42/templates/email_sent.html
@@ -0,0 +1,6 @@
+{% extends 'base.html' %}
+
+{%  block content %}
+<h2>Активация аккаунта</h2>
+<p>Письмо со ссылкой отправлено по указанному адресу.</p>
+{% endblock %}
diff --git a/later42/tokens.py b/later42/tokens.py
new file mode 100644
index 0000000..a2fa72e
--- /dev/null
+++ b/later42/tokens.py
@@ -0,0 +1,13 @@
+from django.contrib.auth.tokens import PasswordResetTokenGenerator
+import six
+
+
+class TokenGenerator(PasswordResetTokenGenerator):
+    def _make_hash_value(self, user, timestamp):
+        return (
+            six.text_type(user.pk) + six.text_type(timestamp) +
+            six.text_type(user.is_active)
+        )
+
+
+account_activation_token = TokenGenerator()
diff --git a/later42/urls.py b/later42/urls.py
index eb9de87..39d2929 100644
--- a/later42/urls.py
+++ b/later42/urls.py
@@ -22,7 +22,7 @@ from django.contrib.auth.views import LoginView
 from rest_framework import routers, serializers, viewsets
 
 from later42.forms import CustomLoginForm
-from later42.views import index, profile, api, api_token, reader, signup, about
+from later42.views import account_activation, index, profile, api, api_token, reader, signup, about
 
 
 class UserSerializer(serializers.HyperlinkedModelSerializer):
@@ -42,6 +42,8 @@ router.register(r'users', UserViewSet)
 urlpatterns = [
     path('admin/', admin.site.urls),
     path('signup/', signup.register, name='signup'),
+    path(r'^activate/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
+         account_activation.activate, name='activate'),
     path("accounts/login/",
          LoginView.as_view(authentication_form=CustomLoginForm), name="login"),
     path("accounts/", include("django.contrib.auth.urls")),
diff --git a/later42/views/account_activation.py b/later42/views/account_activation.py
new file mode 100644
index 0000000..4eb7ccf
--- /dev/null
+++ b/later42/views/account_activation.py
@@ -0,0 +1,29 @@
+import django
+from django.core.mail import EmailMessage
+from django.contrib.auth.models import User
+from later42.tokens import account_activation_token
+from django.template.loader import render_to_string
+from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
+from django.http import HttpResponse
+from django.shortcuts import render, redirect
+from django.contrib.auth import login, authenticate
+from later42.forms import SignUpForm
+from django.contrib.sites.shortcuts import get_current_site
+from django.utils.encoding import force_bytes
+from django.utils.encoding import force_str
+django.utils.encoding.force_text = force_str
+
+
+def activate(request, uidb64, token):
+    try:
+        uid = django.utils.encoding.force_text(urlsafe_base64_decode(uidb64))
+        user = User.objects.get(pk=uid)
+    except (TypeError, ValueError, OverflowError, User.DoesNotExist):
+        user = None
+    if user is not None and account_activation_token.check_token(user, token):
+        user.is_active = True
+        user.save()
+        login(request, user)
+        return redirect('index')
+    else:
+        return HttpResponse('Activation link is invalid!')
diff --git a/later42/views/signup.py b/later42/views/signup.py
index 4ac27d7..a6a5bf5 100644
--- a/later42/views/signup.py
+++ b/later42/views/signup.py
@@ -1,21 +1,43 @@
 from django.contrib.auth import authenticate, login
 from django.shortcuts import redirect, render
+from django.contrib.sites.shortcuts import get_current_site
+from django.template.loader import render_to_string
+from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
+from django.utils.encoding import force_bytes, force_text
+from django.core.mail import EmailMessage
 from later42.forms import SignUpForm
+from later42.tokens import account_activation_token
 
 
 def register(request):
     if request.method == 'POST':
         form = SignUpForm(request.POST)
         if form.is_valid():
-            user = form.save()
-            user.refresh_from_db()
+            user = form.save(commit=False)
+            # user.refresh_from_db()
+            user.is_active = False
             user.save()
             raw_password = form.cleaned_data.get('password1')
 
-            user = authenticate(username=user.username, password=raw_password)
-            login(request, user)
+            current_site = get_current_site(request)
 
-            return redirect('index')
+            # user = authenticate(username=user.username, password=raw_password)
+            # login(request, user)
+
+            mail_subject = 'Later42: Активация аккаунта'
+            message = render_to_string('email_activation.html', {
+                'user': user,
+                'domain': current_site.domain,
+                'uid': urlsafe_base64_encode(force_bytes(user.pk)),
+                'token': account_activation_token.make_token(user),
+            })
+            to_email = form.cleaned_data.get('email')
+            email = EmailMessage(
+                mail_subject, message, to=[to_email]
+            )
+            email.send()
+
+            return render(request, 'email_sent.html')
     else:
         form = SignUpForm()
     return render(request, 'signup.html', {'form': form})
diff --git a/requirements.txt b/requirements.txt
index a392712..be382a6 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,3 +6,4 @@ requests==2.28.1
 djangorestframework==3.14.0
 whitenoise==6.2.0
 psycopg2-binary==2.9.4
+six==1.16.0