Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding user management #88

Merged
merged 1 commit into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
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
Loading