diff --git a/.github/workflows/black_linter.yml b/.github/workflows/black_linter.yml index ea8df899..58a9870f 100644 --- a/.github/workflows/black_linter.yml +++ b/.github/workflows/black_linter.yml @@ -25,7 +25,7 @@ jobs: python -m pip install --upgrade pip pip install black==19.3b pytest pip install click==8.0.2 - pip install -r app/requirements/mysql.txt + pip install -r app/requirements/postgres.txt - name: Analysing the code with pylint run: | black --check . diff --git a/README.md b/README.md index 4db1b2c6..b00671ad 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ SQL_USER= SQL_PASSWORD= SQL_HOST=localhost SQL_PORT=5432 +DEBUG=(True|False) ``` 8. Add an environment variable `MVS_HOST_API` and set the url of the simulation server you wish to use for your models 9. Execute the `local_setup.sh` file (`. local_setup.sh` on linux/mac `bash local_setup.sh` on windows) you might have to make it executable first. Answer yes to the question diff --git a/app/epa/settings.py b/app/epa/settings.py index 60f70984..e4b8e240 100644 --- a/app/epa/settings.py +++ b/app/epa/settings.py @@ -11,11 +11,11 @@ """ import ast import os -from django.contrib.messages import constants as messages +from django.contrib.messages import constants as messages # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = ast.literal_eval(os.getenv("DEBUG", "True")) +DEBUG = ast.literal_eval(os.getenv("DEBUG", "False")) # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -40,7 +40,6 @@ "EPA_SECRET_KEY", "v@p9^=@lc3#1u_xtx*^xhrv0l3li1(+8ik^k@g-_bzmexb0$7n" ) - ALLOWED_HOSTS = ["*"] # Application definition @@ -64,7 +63,6 @@ if DEBUG is True: INSTALLED_APPS.append("sass_processor") - MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", @@ -125,7 +123,6 @@ DEFAULT_AUTO_FIELD = "django.db.models.AutoField" - # Password validation # https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators @@ -155,7 +152,6 @@ USE_TZ = False - # Other configs AUTH_USER_MODEL = "users.CustomUser" @@ -166,7 +162,10 @@ CRISPY_TEMPLATE_PACK = "bootstrap4" -EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" +if DEBUG is True: + EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" +else: + EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" MAILER_EMAIL_BACKEND = EMAIL_BACKEND DEFAULT_FROM_EMAIL = "noreply@elandh2020.eu" diff --git a/app/templates/registration/login.html b/app/templates/registration/login.html index 0edbb81d..f0872372 100644 --- a/app/templates/registration/login.html +++ b/app/templates/registration/login.html @@ -9,32 +9,31 @@ {% block index %} -
- -
+ + + + + {% endblock index %} diff --git a/app/templates/registration/password_reset_complete.html b/app/templates/registration/password_reset_complete.html new file mode 100644 index 00000000..add6706d --- /dev/null +++ b/app/templates/registration/password_reset_complete.html @@ -0,0 +1,13 @@ +{% extends 'base.html' %} +{% load i18n %} + +{% block content %} + +
+

{% trans "Password reset complete" %}

+
+

{% trans "Your password has been set. You may go ahead and log in now." %}

+ {% trans "Log in" %} +
+ +{% endblock %} diff --git a/app/templates/registration/password_reset_confirm.html b/app/templates/registration/password_reset_confirm.html new file mode 100644 index 00000000..2cca889e --- /dev/null +++ b/app/templates/registration/password_reset_confirm.html @@ -0,0 +1,19 @@ +{% extends 'base.html' %} +{% load i18n %} + +{% block content %} + + {% load crispy_forms_tags %} + +
+

{% trans "Password Reset Confirm" %}

+
+

{% trans "Please enter your new password." %}

+
+ {% csrf_token %} + {{ form|crispy }} + +
+
+ +{% endblock %} diff --git a/app/templates/registration/password_reset_done.html b/app/templates/registration/password_reset_done.html new file mode 100644 index 00000000..6af290af --- /dev/null +++ b/app/templates/registration/password_reset_done.html @@ -0,0 +1,14 @@ +{% extends 'base.html' %} +{% load i18n %} + +{% block content %} + +
+

{% trans "Password reset sent" %}

+
+

{% trans "We've emailed you instructions for setting your password, if an account exists with the email you entered. You should receive them shortly." %}

+

{% trans "If you don't receive an email, please make sure you've entered the address you registered with, and check your spam folder." %} +

+
+ +{% endblock %} diff --git a/app/templates/registration/password_reset_form.html b/app/templates/registration/password_reset_form.html new file mode 100644 index 00000000..22a261c7 --- /dev/null +++ b/app/templates/registration/password_reset_form.html @@ -0,0 +1,19 @@ +{% extends 'base.html' %} +{% load i18n %} + +{% block content %} + + {% load crispy_forms_tags %} + +
+

{% trans "Reset Password" %}

+
+

{% trans "Forgotten your password? Enter your email address below, and we'll email instructions for setting a new one." %}

+
+ {% csrf_token %} + {{ form|crispy }} + +
+
+ +{% endblock %} diff --git a/app/templates/registration/signup.html b/app/templates/registration/signup.html index ccae27cd..78033b74 100644 --- a/app/templates/registration/signup.html +++ b/app/templates/registration/signup.html @@ -27,14 +27,6 @@ - - + + {% endblock %} \ No newline at end of file diff --git a/app/users/forms.py b/app/users/forms.py index cd90f21a..3f874167 100644 --- a/app/users/forms.py +++ b/app/users/forms.py @@ -1,7 +1,5 @@ from django import forms from django.contrib.auth.forms import UserCreationForm, UserChangeForm -from crispy_forms.helper import FormHelper -from crispy_forms.layout import Layout, Field from .models import CustomUser diff --git a/app/users/models.py b/app/users/models.py index dcfbbf64..659ac76b 100644 --- a/app/users/models.py +++ b/app/users/models.py @@ -1,5 +1,4 @@ from django.contrib.auth.models import AbstractUser -from django.db import models class CustomUser(AbstractUser): diff --git a/app/users/tokens.py b/app/users/tokens.py deleted file mode 100644 index 8fc124a2..00000000 --- a/app/users/tokens.py +++ /dev/null @@ -1,9 +0,0 @@ -from django.contrib.auth.tokens import PasswordResetTokenGenerator - - -class AccountActivationTokenGenerator(PasswordResetTokenGenerator): - def _make_hash_value(self, user, timestamp): - return user.pk + timestamp + user.is_active - - -account_activation_token = AccountActivationTokenGenerator() diff --git a/app/users/urls.py b/app/users/urls.py index f2cab5be..ffc674b2 100644 --- a/app/users/urls.py +++ b/app/users/urls.py @@ -1,10 +1,11 @@ from django.urls import path -from .views import * +from .views import signup, change_password, user_info, activate, password_reset_request urlpatterns = [ path("signup/", signup, name="signup"), path("change_password/", change_password, name="change_password"), path("user_info/", user_info, name="user_info"), path("activate///", activate, name="activate"), + path("password_reset", password_reset_request, name="password_reset"), ] diff --git a/app/users/views.py b/app/users/views.py index 27015bf8..e88ff55d 100644 --- a/app/users/views.py +++ b/app/users/views.py @@ -1,37 +1,33 @@ +from django.conf import settings +from django.contrib import messages +from django.contrib.auth import update_session_auth_hash, get_user_model from django.contrib.auth.decorators import login_required +from django.contrib.auth.forms import PasswordChangeForm, PasswordResetForm from django.contrib.auth.models import User from django.contrib.auth.tokens import default_token_generator from django.contrib.sites.shortcuts import get_current_site -from django.core.mail import EmailMessage +from django.core.mail import EmailMessage, send_mail, BadHeaderError +from django.db.models import Q from django.http import HttpResponse -from django.http.response import HttpResponseRedirect +from django.shortcuts import render, redirect from django.template.loader import render_to_string -from django.urls import reverse_lazy -from django.urls.base import reverse from django.utils.encoding import force_bytes from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode from django.views.decorators.http import require_http_methods -from django.views.generic import CreateView, UpdateView -from django.contrib import messages -from django.contrib.auth import update_session_auth_hash, get_user_model -from django.contrib.auth.forms import PasswordChangeForm -from django.shortcuts import render, redirect - from .forms import CustomUserCreationForm, CustomUserChangeForm +from .models import CustomUser -from .tokens import account_activation_token - +DEFAULT_FROM_EMAIL = settings.DEFAULT_FROM_EMAIL +EMAIL_HOST = settings.EMAIL_HOST UserModel = get_user_model() def signup(request): if request.method == "POST": form = CustomUserCreationForm(request.POST) - # print(form.errors.as_data()) if form.is_valid(): user = form.save(commit=False) - # user.is_active = False - user.is_active = True + user.is_active = False user.save() current_site = get_current_site(request) mail_subject = "Activate your account." @@ -46,9 +42,9 @@ def signup(request): ) to_email = form.cleaned_data.get("email") email = EmailMessage(mail_subject, message, to=[to_email]) - # email.send() - # return HttpResponse('Please confirm your email address to complete the registration') - return HttpResponseRedirect(reverse("login")) + email.send() + messages.info(request, "Please confirm your email address to complete the registration") + return redirect("home") else: form = CustomUserCreationForm() return render(request, "registration/signup.html", {"form": form}) @@ -63,11 +59,11 @@ def activate(request, uidb64, token): if user is not None and default_token_generator.check_token(user, token): user.is_active = True user.save() - return HttpResponse( - "Thank you for your email confirmation. Now you can login your account." - ) + messages.success(request, "Thank you for your email confirmation. Now you can login your account.") + return redirect("login") else: return HttpResponse("Activation link is invalid!") + return redirect("home") @login_required @@ -101,3 +97,42 @@ def change_password(request): else: form = PasswordChangeForm(request.user) return render(request, "registration/change_password.html", {"form": form}) + + +def password_reset_request(request): + if request.method == "POST": + password_reset_form = PasswordResetForm(request.POST) + if password_reset_form.is_valid(): + data = password_reset_form.cleaned_data["email"] + associated_users = CustomUser.objects.filter(Q(email=data)) + if associated_users.exists(): + for user in associated_users: + subject = "Password Reset Requested" + email_template_name = "registration/password_reset_email.txt" + c = { + "email": user.email, + "domain": EMAIL_HOST, + "site_name": "open_plan", + "uid": urlsafe_base64_encode(force_bytes(user.pk)), + "user": user, + "token": default_token_generator.make_token(user), + "protocol": "http", + } + email = render_to_string(email_template_name, c) + try: + send_mail( + subject, + email, + DEFAULT_FROM_EMAIL, + [user.email], + fail_silently=False, + ) + except BadHeaderError: + return HttpResponse("Invalid header found.") + return redirect("/password_reset/done/") + password_reset_form = PasswordResetForm() + return render( + request=request, + template_name="registration/password_reset_form.html", + context={"password_reset_form": password_reset_form}, + )