Skip to content

Commit

Permalink
Merge pull request #88 from SADiLaR/feature/user-management
Browse files Browse the repository at this point in the history
Adding user management
  • Loading branch information
daniel-gray-tangent authored Jul 4, 2024
2 parents d77a34b + de85130 commit 8973156
Show file tree
Hide file tree
Showing 21 changed files with 353 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,10 @@ LOGGING_LOGGERS_LEVEL=INFO
LOGGING_LOGGERS_DJANGO_LEVEL=INFO
TESTING_DIR=/app/general/tests/files/
FEATURE_FLAG=''
EMAIL_HOST=''
EMAIL_USE_TLS=True
EMAIL_PORT=587
EMAIL_HOST_USER=''
EMAIL_HOST_PASSWORD=''
EMAIL_BACKEND_CONSOLE='True/False'
EMAIL_USE_TLS=True
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,19 @@ About the project:

---

### Email Settings in Development

.env file

* EMAIL_HOST='sandbo x.smtp.mailtrap.io'
* EMAIL_HOST_USER='*********'
* EMAIL_HOST_PASSWORD='******'
* EMAIL_PORT='2525'
* EMAIL_BACKEND_CONSOLE=True

By default, the email backend is set to console, so you can see the email in the console.
To send an email, you need to set the EMAIL_BACKEND_CONSOLE to False.

### Plugins installed

#### Django Simple History
Expand Down Expand Up @@ -58,3 +71,8 @@ Docker Volumes for production:
* /logging
* /pdf_uploads
* /pdf_upload_completed

### Email Settings in Production

.env file
* EMAIL_BACKEND_CONSOLE=False
Empty file added app/accounts/__init__.py
Empty file.
3 changes: 3 additions & 0 deletions app/accounts/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.contrib import admin

# Register your models here.
6 changes: 6 additions & 0 deletions app/accounts/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class AccountsConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "accounts"
22 changes: 22 additions & 0 deletions app/accounts/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from django import forms
from django.contrib.auth.forms import UserCreationForm

from users.models import CustomUser


class CustomUserCreationForm(UserCreationForm):
email = forms.EmailField(required=True, help_text="Required. Add a valid email address.")
username = forms.CharField(required=True, help_text="Required. Add a valid username.")
first_name = forms.CharField(required=True, help_text="Required. Add a valid first name.")
last_name = forms.CharField(required=True, help_text="Required. Add a valid last name.")

class Meta:
model = CustomUser
fields = ("username", "email", "first_name", "last_name")

def __init__(self, *args, **kwargs):
super(CustomUserCreationForm, self).__init__(*args, **kwargs)
self.fields["email"].required = True
self.fields["username"].required = True
self.fields["first_name"].required = True
self.fields["last_name"].required = True
Empty file.
3 changes: 3 additions & 0 deletions app/accounts/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.db import models

# Create your models here.
Empty file added app/accounts/tests/__init__.py
Empty file.
88 changes: 88 additions & 0 deletions app/accounts/tests/test_signup_form.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import unittest

from django.test import TestCase

from accounts.forms import CustomUserCreationForm


class CustomUserCreationFormTest(TestCase):
def setUp(self):
self.username = "testuser"
self.email = "[email protected]"
self.first_name = "Test"
self.last_name = "User"
self.password1 = "sadilar2024"
self.password2 = "sadilar2024"

def test_valid_data(self):
form = CustomUserCreationForm(
{
"username": self.username,
"email": self.email,
"first_name": self.first_name,
"last_name": self.last_name,
"password1": self.password1,
"password2": self.password2,
}
)

self.assertTrue(form.is_valid())

def test_blank_data(self):
form = CustomUserCreationForm({})
self.assertFalse(form.is_valid())

self.assertEqual(
form.errors,
{
"username": ["This field is required."],
"email": ["This field is required."],
"first_name": ["This field is required."],
"last_name": ["This field is required."],
"password1": ["This field is required."],
"password2": ["This field is required."],
},
)

def test_invalid_email(self):
form = CustomUserCreationForm(
{
"username": self.username,
"email": "not a valid email",
"first_name": self.first_name,
"last_name": self.last_name,
"password1": self.password1,
"password2": self.password2,
}
)
self.assertFalse(form.is_valid())
self.assertEqual(
form.errors,
{
"email": ["Enter a valid email address."],
},
)

def test_passwords_do_not_match(self):
form = CustomUserCreationForm(
{
"username": self.username,
"email": self.email,
"first_name": self.first_name,
"last_name": self.last_name,
"password1": self.password1,
"password2": "wrong password",
}
)

self.assertFalse(form.is_valid())
self.assertEqual(
form.errors,
{
"password2": ["The two password fields didn’t match."],
},
)


if __name__ == "__main__":
unittest.main()
33 changes: 33 additions & 0 deletions app/accounts/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from django.contrib.auth import views as auth_views
from django.urls import path

from . import views

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"),
name="password_reset",
),
path(
"password_reset/done/",
auth_views.PasswordResetDoneView.as_view(template_name="accounts/password_reset_done.html"),
name="password_reset_done",
),
path(
"reset/<uidb64>/<token>/",
auth_views.PasswordResetConfirmView.as_view(
template_name="accounts/password_reset_confirm.html"
),
name="password_reset_confirm",
),
path(
"reset/done/",
auth_views.PasswordResetCompleteView.as_view(
template_name="accounts/password_reset_complete.html"
),
name="password_reset_complete",
),
]
18 changes: 18 additions & 0 deletions app/accounts/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from django.contrib.auth import login
from django.shortcuts import redirect, render

from .forms import CustomUserCreationForm


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.save()
login(request, user)
return redirect("home")
else:
form = CustomUserCreationForm()
return render(request, "accounts/register.html", {"form": form})
15 changes: 15 additions & 0 deletions app/app/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"users",
"general",
"simple_history",
"accounts",
]

# Add django-extensions to the installed apps if DEBUG is True
Expand Down Expand Up @@ -125,6 +126,20 @@
"host.docker.internal",
]

# Email settings
EMAIL_HOST = os.environ.get("EMAIL_HOST")
EMAIL_HOST_USER = os.environ.get("EMAIL_HOST_USER")
EMAIL_HOST_PASSWORD = os.environ.get("EMAIL_HOST_PASSWORD")
EMAIL_PORT = os.environ.get("EMAIL_PORT")
EMAIL_USE_TLS = os.environ.get("EMAIL_USE_TLS")

email_backend_env = os.environ.get("EMAIL_BACKEND_CONSOLE", "False").lower() in ["true", "1", "yes"]

if DEBUG and email_backend_env:
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"

LOGIN_REDIRECT_URL = "/"
LOGOUT_REDIRECT_URL = "/"

# Password validation
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
Expand Down
3 changes: 3 additions & 0 deletions app/app/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
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 @@ -39,6 +40,8 @@
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")),
]

if settings.DEBUG:
Expand Down
22 changes: 22 additions & 0 deletions app/templates/accounts/login.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{% extends "base.html" %}

{% block title %}Log In{% endblock %}

{% block content %}
<div class="container">
<h2>Log In</h2>
<form method="post">
{% csrf_token %}
{{ form }}
<button type="submit">Log In</button>
</form>

<br>
<hr>
<form method="post" action="{% url 'logout' %}">
{% csrf_token %}
<button type="submit">logout</button>
</form>
</div>

{% endblock %}
11 changes: 11 additions & 0 deletions app/templates/accounts/password_reset_complete.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{% extends 'base.html' %}

{% block title %}Password reset complete{% endblock %}

{% block content %}
<div class="container">
<p>Your password has been set. You may go ahead and log in now.</p>
<br>
<p><a href="{{ login_url }}">Log in</a></p>
</div>
{% endblock %}
43 changes: 43 additions & 0 deletions app/templates/accounts/password_reset_confirm.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{% extends "base.html" %}

{% block title %}Enter new password{% endblock %}

{% block content %}

{% if validlink %}

<div class="container">
<p>Please enter your new password twice so we can verify you typed it in correctly.</p>

<form method="post">{% csrf_token %}
<fieldset class="module aligned">
<div class="form-row field-password1">
{{ form.new_password1.errors }}
<div class="flex-container">
<label for="id_new_password1">New password:</label>
{{ form.new_password1 }}
</div>
</div>
<div class="form-row field-password2">
{{ form.new_password2.errors }}
<div class="flex-container">
<label for="id_new_password2">Confirm password:</label>
{{ form.new_password2 }}
</div>
</div>
</fieldset>
<br>
<br>
<div class="submit-row">
<input type="submit" value="Change my password">
</div>
</form>

{% else %}

<p>"The password reset link was invalid, possibly because it has already been used. Please request a new
password reset.</p>

{% endif %}
</div>
{% endblock %}
13 changes: 13 additions & 0 deletions app/templates/accounts/password_reset_done.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{% extends "base.html" %}

{% block title %}Email Sent{% endblock %}

{% block content %}
<div class="container">
<p>We’ve emailed you instructions for setting your password, if an account exists with the email you entered.
You should receive them shortly.</p>

<p>If you don’t receive an email, please make sure you’ve entered the address you registered with, and check
your spam folder.</p>
</div>
{% endblock %}
28 changes: 28 additions & 0 deletions app/templates/accounts/password_reset_form.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{% extends 'base.html' %}

{% block title %}Forgot Your Password?{% endblock %}

{% block content %}
<div class="container">
<p>Forgotten your password? Enter your email address below, and we’ll email instructions for setting a new
one.</p>

<form method="post">
{% csrf_token %}
<fieldset class="module aligned">
<div class="form-row field-email">
{{ form.email.errors }}
<div class="flex-container">
<label for="id_email">Email address:</label>
{{ form.email }}
</div>
</div>
</fieldset>
<div class="submit-row">
<br>
<br>
<input type="submit" value="Reset my password">
</div>
</form>
</div>
{% endblock %}
Loading

0 comments on commit 8973156

Please sign in to comment.