Skip to content

Commit

Permalink
Updating user Managerment
Browse files Browse the repository at this point in the history
  • Loading branch information
daniel-gray-tangent committed Jul 18, 2024
1 parent b248001 commit 3f32bb1
Show file tree
Hide file tree
Showing 20 changed files with 511 additions and 36 deletions.
Empty file.
32 changes: 32 additions & 0 deletions app/accounts/service/active_email.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from django.contrib.sites.shortcuts import get_current_site
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode

from accounts.tokens import account_activation_token


class SendActiveEmailService:
@staticmethod
def send_activation_email(request, user):
if user and request:
current_site = get_current_site(request)
mail_subject = "Activate your account."
message = render_to_string(
"accounts/email/activation_email.html",
{
"user": user,
"domain": current_site.domain,
"uid": urlsafe_base64_encode(force_bytes(user.pk)),
"token": account_activation_token.make_token(user),
},
)
text_content = (
"Please activate your account by clicking the link provided in the email."
)
email = EmailMultiAlternatives(
mail_subject, text_content, "[email protected]", [user.email]
)
email.attach_alternative(message, "text/html")
email.send()
54 changes: 54 additions & 0 deletions app/accounts/tests/test_active_email.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from unittest.mock import MagicMock, patch

from django.contrib.sites.shortcuts import get_current_site
from django.core import mail
from django.test import RequestFactory, TestCase
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode

from accounts.service.active_email import ( # Adjust the import path as necessary
SendActiveEmailService,
)
from accounts.tokens import account_activation_token
from users.models import CustomUser # Import your custom user model


class SendActiveEmailServiceTest(TestCase):
def setUp(self):
self.factory = RequestFactory()
self.user = CustomUser.objects.create_user(
username="testuser", email="[email protected]", password="password123"
)
self.request = self.factory.get("/fake-path")
self.service = SendActiveEmailService()

def test_send_activation_email(self):
with patch("accounts.service.active_email.render_to_string") as mock_render:
# Set up the mocks
mock_render.return_value = "<html>mocked template</html>"

# # Call the method
self.service.send_activation_email(self.request, self.user)

# Check that render_to_string was called with the correct parameters
mock_render.assert_called_once_with(
"accounts/email/activation_email.html",
{
"user": self.user,
"domain": get_current_site(self.request).domain,
"uid": urlsafe_base64_encode(force_bytes(self.user.pk)),
"token": account_activation_token.make_token(self.user),
},
)

# Check that an email was sent
self.assertEqual(len(mail.outbox), 1)
sent_email = mail.outbox[0]
self.assertEqual(sent_email.subject, "Activate your account.")
self.assertEqual(sent_email.to, [self.user.email])
self.assertIn("mocked template", sent_email.alternatives[0][0])
self.assertEqual(sent_email.alternatives[0][1], "text/html")
self.assertIn(
"Please activate your account by clicking the link provided in the email.",
sent_email.body,
)
40 changes: 40 additions & 0 deletions app/accounts/tests/test_token.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import unittest
from datetime import datetime

import six
from django.contrib.auth.tokens import PasswordResetTokenGenerator

from accounts.tokens import AccountActivationTokenGenerator


# Speculate user model class for test abstraction
class User:
def __init__(self, id, is_active):
self.pk = id
self.is_active = is_active


class TestAccountActivationTokenGenerator(unittest.TestCase):
def setUp(self):
self.generator = AccountActivationTokenGenerator()
self.timestamp = datetime.now()

def test_make_hash_value_active_user(self):
user = User(1, True)
hash_val = self.generator._make_hash_value(user, self.timestamp)
expected_val = (
six.text_type(user.pk) + six.text_type(self.timestamp) + six.text_type(user.is_active)
)
self.assertEqual(hash_val, expected_val)

def test_make_hash_value_inactive_user(self):
user = User(1, False)
hash_val = self.generator._make_hash_value(user, self.timestamp)
expected_val = (
six.text_type(user.pk) + six.text_type(self.timestamp) + six.text_type(user.is_active)
)
self.assertEqual(hash_val, expected_val)


if __name__ == "__main__":
unittest.main()
10 changes: 10 additions & 0 deletions app/accounts/tokens.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import six
from django.contrib.auth.tokens import PasswordResetTokenGenerator


class AccountActivationTokenGenerator(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 = AccountActivationTokenGenerator()
12 changes: 10 additions & 2 deletions app/accounts/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@

from . import views

app_name = "accounts"
urlpatterns = [
path("register/", views.register, name="accounts_register"),
path("login/", auth_views.LoginView.as_view(template_name="accounts/login.html"), name="login"),
path(
"password_reset/",
auth_views.PasswordResetView.as_view(template_name="accounts/password_reset_form.html"),
auth_views.PasswordResetView.as_view(
template_name="accounts/password_reset_form.html",
success_url="/accounts/password_reset/done/",
),
name="password_reset",
),
path(
Expand All @@ -19,7 +23,8 @@
path(
"reset/<uidb64>/<token>/",
auth_views.PasswordResetConfirmView.as_view(
template_name="accounts/password_reset_confirm.html"
template_name="accounts/password_reset_confirm.html",
success_url="/accounts/reset/done/",
),
name="password_reset_confirm",
),
Expand All @@ -30,4 +35,7 @@
),
name="password_reset_complete",
),
path("activate/<uidb64>/<token>/", views.activate, name="activate"),
path("activation_sent/", views.activation_sent, name="activation_sent"),
path("resend_activation/", views.resend_activation, name="resend_activation"),
]
62 changes: 58 additions & 4 deletions app/accounts/views.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
from django.contrib.auth import authenticate
from django.contrib.auth import get_user_model
from django.contrib.auth import login as auth_login
from django.shortcuts import redirect, render
from django.utils.encoding import force_str
from django.utils.http import urlsafe_base64_decode

from accounts.service.active_email import SendActiveEmailService

from .forms import CustomAuthenticationForm, CustomUserCreationForm
from .tokens import account_activation_token


def register(request):
if request.method == "POST":
form = CustomUserCreationForm(request.POST)
if form.is_valid():
user = form.save(commit=False)
user.is_staff = True
user.is_staff = False
user.is_active = False
user.save()
auth_login(request, user)
return redirect("home")

SendActiveEmailService.send_activation_email(request, user)

return redirect("accounts:activation_sent")

else:
form = CustomUserCreationForm()
return render(request, "accounts/register.html", {"form": form})
Expand All @@ -30,3 +39,48 @@ def user_login(request):
form = CustomAuthenticationForm()

return render(request, "accounts/login.html", {"form": form})


def activate(request, uidb64, token):
User = get_user_model() # Get the custom user model
try:
uid = force_str(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()
auth_login(request, user)
return render(request, "accounts/activate.html")
else:
return render(request, "accounts/activation_invalid.html")


def activation_sent(request):
return render(request, "accounts/activation_sent.html")


def resend_activation(request):
User = get_user_model() #
if request.method == "POST":
user_email = request.POST["email"]
try:
user = User.objects.get(email=user_email)
if not user.is_active:
SendActiveEmailService.send_activation_email(request, user)

return redirect("accounts:activation_sent")

else:
return render(
request, "accounts/resend_activation.html", {"error": "Email address active."}
)

except User.DoesNotExist:
# Handle the case where the email does not exist
return render(
request, "accounts/resend_activation.html", {"error": "Email address not found."}
)
return render(request, "accounts/resend_activation.html")
4 changes: 1 addition & 3 deletions app/app/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.contrib.auth import views as auth_views
from django.urls import include, path
from django.utils.translation import gettext_lazy as _

Expand All @@ -40,8 +39,7 @@
path("subject/<int:pk>/", views.subject_detail, name="subject_detail"),
path("search/", views.search, name="search"),
path("i18n/", include("django.conf.urls.i18n")),
path("accounts/", include("accounts.urls")),
path("accounts/", include("django.contrib.auth.urls")),
path("accounts/", include("accounts.urls"), name="accounts"),
]

if settings.DEBUG:
Expand Down
14 changes: 14 additions & 0 deletions app/templates/accounts/activate.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

{% extends "base.html" %}
{% load static %}
{% load i18n %}

{% block title %}{% trans "Activated" %}{% endblock %}

{% block content %}
<div class="container">
<h1>Account Activated</h1>
<p>Your account has been successfully activated. You can now log in using your credentials.</p>
<a href="{% url 'accounts:login' %}">Login</a>
</div>
{% endblock %}
21 changes: 21 additions & 0 deletions app/templates/accounts/activation_invalid.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{% extends "base.html" %}
{% load static %}
{% load i18n %}

{% block title %}{% trans "Sign Up" %}{% endblock %}

{% block content %}
<div class="container">
<div class="section mt-3 mb-3">
<div class="card body-card">
<div class="user-account-body">
<h2>Activation Invalid</h2>
<p>Sorry, but the activation link you used is invalid or has expired.</p>
<p>Please request a new activation link or contact support for further assistance.</p>
<a href="{% url 'accounts:accounts_register' %}">Register</a> | <a href="{% url 'accounts:login' %}">Login</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
22 changes: 22 additions & 0 deletions app/templates/accounts/activation_sent.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{% extends "base.html" %}
{% load static %}
{% load i18n %}

{% block title %}{% trans "Activation Email Sent" %}{% endblock %}

{% block content %}
<div class="container">
<div class="section mt-3 mb-3">
<div class="card body-card">
<div class="user-account-body">
<h2>Activation Email Sent</h2>
<p>Thank you for registering. An activation email has been sent to your email address.</p>
<p>Please check your email and click on the activation link to activate your account.</p>
<p>If you did not receive the email, please check your spam folder or
<a href="{% url 'accounts:resend_activation' %}">resend the activation email</a>.</p>
<a href="{% url 'accounts:login' %}">Login</a>
</div>
</div>
</div>
</div>
{% endblock %}
60 changes: 60 additions & 0 deletions app/templates/accounts/email/activation_email.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Activate Your Account</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 0;
}
.container {
width: 100%;
max-width: 600px;
margin: 0 auto;
background-color: #ffffff;
padding: 20px;
border: 1px solid #dddddd;
}
h1 {
color: #333333;
}
p {
color: #666666;
}
a {
color: #1a73e8;
text-decoration: none;
}
.button {
display: inline-block;
padding: 10px 20px;
background-color: #1a73e8;
color: #ffffff;
text-align: center;
border-radius: 5px;
text-decoration: none;
}
.button:hover {
background-color: #1558b0;
}
</style>
</head>
<body>
<div class="container">
<h1>Activate Your Account</h1>
<p>Hi {{ user.username }},</p>
<p>Thank you for signing up for our service. Please click the link below to activate your account:</p>
<p>
<a href="http://{{ domain }}{% url 'accounts:activate' uidb64=uid token=token %}" class="button">
Activate your account
</a>
</p>
<p>If you did not sign up for this account, please ignore this email.</p>
<p>Thank you,</p>
<p>The Team</p>
</div>
</body>
</html>
Loading

0 comments on commit 3f32bb1

Please sign in to comment.