Password recovery implemented
This commit is contained in:
parent
3a24ee2459
commit
12526f44d7
33
__init__.py
33
__init__.py
@ -1,26 +1,29 @@
|
|||||||
from django.urls.base import reverse
|
from django.urls.base import reverse
|
||||||
|
|
||||||
|
|
||||||
def url_login(request):
|
def url_by_name(name, request, just_url=False):
|
||||||
nxt = request.GET.get('next', request.get_full_path())
|
base_url = reverse(name)
|
||||||
return reverse('users-login') + '?next=%s' % nxt
|
if just_url:
|
||||||
|
return base_url
|
||||||
|
else:
|
||||||
|
nxt = request.GET.get('next', request.get_full_path())
|
||||||
|
return base_url + '?next=%s' % nxt
|
||||||
|
|
||||||
|
|
||||||
def url_logout(request):
|
def url_login(request, just_url=False):
|
||||||
nxt = request.GET.get('next', request.get_full_path())
|
return url_by_name('users-login', request, just_url)
|
||||||
return reverse('users-logout') + '?next=%s' % nxt
|
|
||||||
|
|
||||||
|
|
||||||
def url_profile(request):
|
def url_logout(request, just_url=False):
|
||||||
nxt = request.GET.get('next', request.get_full_path())
|
return url_by_name('users-logout', request, just_url)
|
||||||
return reverse('users-profile') + '?next=%s' % nxt
|
|
||||||
|
|
||||||
|
|
||||||
def url_register(request):
|
def url_profile(request, just_url=False):
|
||||||
nxt = request.GET.get('next', request.get_full_path())
|
return url_by_name('users-profile', request, just_url)
|
||||||
return reverse('users-register') + '?next=%s' % nxt
|
|
||||||
|
|
||||||
|
|
||||||
def url_password_recovery(request):
|
def url_recover(request, just_url=False):
|
||||||
nxt = request.GET.get('next', request.get_full_path())
|
return url_by_name('users-recover', request, just_url)
|
||||||
return reverse('users-password-recovery') + '?next=%s' % nxt
|
|
||||||
|
def url_register(request, just_url=False):
|
||||||
|
return url_by_name('users-register', request, just_url)
|
||||||
|
20
context.py
20
context.py
@ -1,13 +1,14 @@
|
|||||||
from django.urls.base import reverse
|
from django.urls.base import reverse
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from themes import empty_entry_parameters, color_icon_url
|
from themes import empty_entry_parameters, color_icon_url
|
||||||
from . import url_login, url_logout, url_register, url_profile
|
from . import url_login, url_logout, url_register, url_profile, url_recover
|
||||||
from . import parameter
|
from . import parameter
|
||||||
|
|
||||||
ADMIN_ENTRY_UID = 'admin-main'
|
ADMIN_ENTRY_UID = 'admin-main'
|
||||||
LOGIN_ENTRY_UID = 'login-main'
|
LOGIN_ENTRY_UID = 'login-main'
|
||||||
LOGOUT_ENTRY_UID = 'logout-main'
|
LOGOUT_ENTRY_UID = 'logout-main'
|
||||||
PROFILE_ENTRY_UID = 'profile-main'
|
PROFILE_ENTRY_UID = 'profile-main'
|
||||||
|
RECOVER_ENTRY_UID = 'recover-main'
|
||||||
REGISTER_ENTRY_UID = 'register-main'
|
REGISTER_ENTRY_UID = 'register-main'
|
||||||
|
|
||||||
|
|
||||||
@ -32,6 +33,7 @@ def menubar(bar, request):
|
|||||||
|
|
||||||
def actionbar(bar, request):
|
def actionbar(bar, request):
|
||||||
bar.append_entry(*login_entry_parameters(request, left=True))
|
bar.append_entry(*login_entry_parameters(request, left=True))
|
||||||
|
bar.append_entry(*recover_entry_parameters(request, left=True))
|
||||||
if parameter.get(parameter.USERS_SELF_REGISTRATION):
|
if parameter.get(parameter.USERS_SELF_REGISTRATION):
|
||||||
bar.append_entry(*register_entry_parameters(request, left=True))
|
bar.append_entry(*register_entry_parameters(request, left=True))
|
||||||
|
|
||||||
@ -43,10 +45,20 @@ def login_entry_parameters(request, left=False):
|
|||||||
color_icon_url(request, 'login.png'), # icon
|
color_icon_url(request, 'login.png'), # icon
|
||||||
url_login(request), # url
|
url_login(request), # url
|
||||||
left, # left
|
left, # left
|
||||||
False # active
|
request.path == url_login(request, True) # active
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def recover_entry_parameters(request, left=False):
|
||||||
|
return (
|
||||||
|
RECOVER_ENTRY_UID, # uid
|
||||||
|
_('Recover'), # name
|
||||||
|
color_icon_url(request, 'recover.png'), # icon
|
||||||
|
url_recover(request), # url
|
||||||
|
left, # left
|
||||||
|
request.path == url_recover(request, True) # active
|
||||||
|
)
|
||||||
|
|
||||||
def register_entry_parameters(request, left=False):
|
def register_entry_parameters(request, left=False):
|
||||||
return (
|
return (
|
||||||
REGISTER_ENTRY_UID, # uid
|
REGISTER_ENTRY_UID, # uid
|
||||||
@ -54,7 +66,7 @@ def register_entry_parameters(request, left=False):
|
|||||||
color_icon_url(request, 'register.png'), # icon
|
color_icon_url(request, 'register.png'), # icon
|
||||||
url_register(request), # url
|
url_register(request), # url
|
||||||
left, # left
|
left, # left
|
||||||
False # active
|
request.path == url_register(request, True) # active
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -87,5 +99,5 @@ def profile_entry_parameters(request):
|
|||||||
color_icon_url(request, 'user.png'), # icon
|
color_icon_url(request, 'user.png'), # icon
|
||||||
url_profile(request), # url
|
url_profile(request), # url
|
||||||
False, # left
|
False, # left
|
||||||
False # active
|
request.path == url_profile(request, True) # active
|
||||||
)
|
)
|
||||||
|
23
emails.py
23
emails.py
@ -30,6 +30,12 @@ def send_validation_failed(uid, token):
|
|||||||
mail_admins(subject, message, fail_silently=True)
|
mail_admins(subject, message, fail_silently=True)
|
||||||
|
|
||||||
|
|
||||||
|
def send_recover_failed(uid, token):
|
||||||
|
subject = "Password recovery failed!"
|
||||||
|
message = f"uid = {uid}\ntoken = '{token}'"
|
||||||
|
mail_admins(subject, message, fail_silently=True)
|
||||||
|
|
||||||
|
|
||||||
def send_welcome_mail(new_user: User):
|
def send_welcome_mail(new_user: User):
|
||||||
subject = "Welcome!"
|
subject = "Welcome!"
|
||||||
message = """Hello %(username)s.
|
message = """Hello %(username)s.
|
||||||
@ -56,3 +62,20 @@ def send_validation_mail(new_user, request):
|
|||||||
[new_user.email],
|
[new_user.email],
|
||||||
)
|
)
|
||||||
send_mail(email_subject, message2, settings.EMAIL_FROM, [new_user.email], fail_silently=True)
|
send_mail(email_subject, message2, settings.EMAIL_FROM, [new_user.email], fail_silently=True)
|
||||||
|
|
||||||
|
|
||||||
|
def send_recover_mail(user, request):
|
||||||
|
email_subject = "Password recovery"
|
||||||
|
message2 = render_to_string('users/email_recover.html', {
|
||||||
|
'name': user.first_name,
|
||||||
|
'base_url': request.build_absolute_uri('/')[:-1],
|
||||||
|
'uid': urlsafe_base64_encode(force_bytes(user.pk)),
|
||||||
|
'token': generate_token.make_token(user)
|
||||||
|
})
|
||||||
|
email = EmailMessage(
|
||||||
|
email_subject,
|
||||||
|
message2,
|
||||||
|
settings.EMAIL_HOST_USER,
|
||||||
|
[user.email],
|
||||||
|
)
|
||||||
|
send_mail(email_subject, message2, settings.EMAIL_FROM, [user.email], fail_silently=True)
|
||||||
|
10
forms.py
10
forms.py
@ -1,11 +1,19 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.auth.forms import UserCreationForm, UserChangeForm, PasswordChangeForm
|
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm, UserChangeForm, PasswordChangeForm, SetPasswordForm
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from .models import UserProfile, get_userprofile
|
from .models import UserProfile, get_userprofile
|
||||||
from users import emails
|
from users import emails
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordRecoverForm(AuthenticationForm):
|
||||||
|
password = None
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordRecoverChangeForm(SetPasswordForm):
|
||||||
|
fields = ["new_password1", "new_password2"]
|
||||||
|
|
||||||
|
|
||||||
class UserRegistrationForm(UserCreationForm):
|
class UserRegistrationForm(UserCreationForm):
|
||||||
email = forms.EmailField()
|
email = forms.EmailField()
|
||||||
|
|
||||||
|
8
templates/users/email_recover.html
Normal file
8
templates/users/email_recover.html
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{% autoescape off %}
|
||||||
|
|
||||||
|
Hello {{ name }},
|
||||||
|
|
||||||
|
you are able to recover your password by clicking on the following link.
|
||||||
|
Recovery Link: {{ base_url }}{% url 'users-recover-token' uidb64=uid token=token %}
|
||||||
|
|
||||||
|
{% endautoescape %}
|
11
templates/users/recover.html
Normal file
11
templates/users/recover.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{% extends "themes/"|add:settings.page_theme|add:"/base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<form class="form" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form.as_p }}
|
||||||
|
<input type="submit" value="{% trans "Recover" %}" class="button" />
|
||||||
|
</form>
|
||||||
|
{% endblock content %}
|
3
urls.py
3
urls.py
@ -4,9 +4,10 @@ from . import views
|
|||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('login', views.login, name='users-login'),
|
path('login', views.login, name='users-login'),
|
||||||
path('logout', views.logout, name='users-logout'),
|
path('logout', views.logout, name='users-logout'),
|
||||||
path('password_recovery', views.password_recovery, name='users-password-recovery'),
|
|
||||||
path('profile', views.profile, name='users-profile'),
|
path('profile', views.profile, name='users-profile'),
|
||||||
path('register', views.register, name='users-register'),
|
path('register', views.register, name='users-register'),
|
||||||
|
path('recover', views.recover, name='users-recover'),
|
||||||
|
path('recover-token/<uidb64>/<token>', views.recover_token, name='users-recover-token'),
|
||||||
path('validate/<uidb64>/<token>', views.validate, name='users-validate'),
|
path('validate/<uidb64>/<token>', views.validate, name='users-validate'),
|
||||||
path('activate/<pk>', views.activate, name='users-activate'),
|
path('activate/<pk>', views.activate, name='users-activate'),
|
||||||
]
|
]
|
||||||
|
67
views.py
67
views.py
@ -11,7 +11,7 @@ from django.contrib.auth.models import User
|
|||||||
from django.utils.encoding import force_str
|
from django.utils.encoding import force_str
|
||||||
from django.utils.http import urlsafe_base64_decode
|
from django.utils.http import urlsafe_base64_decode
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from .forms import UserRegistrationForm, UserProfileForm, UserActivationForm, UserPasswordChangeForm
|
from .forms import PasswordRecoverForm, UserRegistrationForm, UserProfileForm, UserActivationForm, UserPasswordChangeForm, PasswordRecoverChangeForm
|
||||||
import logging
|
import logging
|
||||||
from .models import get_userprofile
|
from .models import get_userprofile
|
||||||
from themes import Context
|
from themes import Context
|
||||||
@ -59,6 +59,26 @@ def profile(request):
|
|||||||
)
|
)
|
||||||
return render(request, 'users/profile.html', context=context)
|
return render(request, 'users/profile.html', context=context)
|
||||||
|
|
||||||
|
def recover(request):
|
||||||
|
context = Context(request) # needs to be executed first because of time mesurement
|
||||||
|
context_adaption(context, request, _('Password Recovery'))
|
||||||
|
if not request.POST:
|
||||||
|
form = PasswordRecoverForm(request)
|
||||||
|
else:
|
||||||
|
username = request.POST.get("username")
|
||||||
|
try:
|
||||||
|
user = User.objects.get(username=username)
|
||||||
|
except User.DoesNotExist:
|
||||||
|
pass # hide non existing user (just do nothing)
|
||||||
|
else:
|
||||||
|
profile = get_userprofile(user)
|
||||||
|
if profile.mail_validated:
|
||||||
|
emails.send_recover_mail(user, request)
|
||||||
|
#
|
||||||
|
messages.info(request, _("If the user exists, you will get a reover email."))
|
||||||
|
return redirect("users-login")
|
||||||
|
context['form'] = form
|
||||||
|
return render(request, 'users/recover.html', context)
|
||||||
|
|
||||||
def register(request):
|
def register(request):
|
||||||
context = Context(request) # needs to be executed first because of time mesurement
|
context = Context(request) # needs to be executed first because of time mesurement
|
||||||
@ -116,10 +136,10 @@ def login(request):
|
|||||||
if is_active:
|
if is_active:
|
||||||
if parameter.get(parameter.USERS_SELF_REGISTRATION):
|
if parameter.get(parameter.USERS_SELF_REGISTRATION):
|
||||||
messages.error(request, _('Login failed! You can do a password recorvery <a href="%(url_recover)s">here</a> or you can register <a href="%(url_register)s">here</a>.') %
|
messages.error(request, _('Login failed! You can do a password recorvery <a href="%(url_recover)s">here</a> or you can register <a href="%(url_register)s">here</a>.') %
|
||||||
{'url_register': users.url_register(request), 'url_recover': users.url_password_recovery(request)})
|
{'url_register': users.url_register(request), 'url_recover': users.url_recover(request)})
|
||||||
else:
|
else:
|
||||||
messages.error(request, _('Login failed! You can do a password recorvery <a href="%(url_recover)s">here</a>.') %
|
messages.error(request, _('Login failed! You can do a password recorvery <a href="%(url_recover)s">here</a>.') %
|
||||||
{'url_recover': users.url_password_recovery(request)})
|
{'url_recover': users.url_recover(request)})
|
||||||
else:
|
else:
|
||||||
messages.info(request, _("The account is deactivated. Confirm your email adress and wait for the administrator to activate your account."))
|
messages.info(request, _("The account is deactivated. Confirm your email adress and wait for the administrator to activate your account."))
|
||||||
|
|
||||||
@ -227,3 +247,44 @@ def activate(request, pk):
|
|||||||
user_to_be_activated.delete()
|
user_to_be_activated.delete()
|
||||||
messages.info(request, _("User deleted."))
|
messages.info(request, _("User deleted."))
|
||||||
return redirect("/")
|
return redirect("/")
|
||||||
|
|
||||||
|
|
||||||
|
def recover_token(request, uidb64, token):
|
||||||
|
context = Context(request) # needs to be executed first because of time mesurement
|
||||||
|
print(settings.PASSWORD_RESET_TIMEOUT)
|
||||||
|
try:
|
||||||
|
uid = force_str(urlsafe_base64_decode(uidb64))
|
||||||
|
except (TypeError, ValueError, OverflowError, User.DoesNotExist):
|
||||||
|
uid = None
|
||||||
|
myuser = None
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
myuser = User.objects.get(pk=uid)
|
||||||
|
except User.DoesNotExist:
|
||||||
|
myuser = None
|
||||||
|
|
||||||
|
if myuser is not None and generate_token.check_token(myuser, token):
|
||||||
|
if request.POST:
|
||||||
|
form = PasswordRecoverChangeForm(myuser, data=request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
form.save()
|
||||||
|
return redirect(request.GET.get('next') or 'users-login')
|
||||||
|
else:
|
||||||
|
form = PasswordRecoverChangeForm(myuser)
|
||||||
|
#
|
||||||
|
context_adaption(
|
||||||
|
context,
|
||||||
|
request,
|
||||||
|
_('Password recovery for %(username)s') % {'username': myuser.username},
|
||||||
|
form=form,
|
||||||
|
)
|
||||||
|
return render(request, 'users/recover.html', context=context)
|
||||||
|
else:
|
||||||
|
context_adaption(
|
||||||
|
context,
|
||||||
|
request,
|
||||||
|
_('Recovery failed'),
|
||||||
|
)
|
||||||
|
messages.info(request, _("Recovery failed. The system administrator will be informed."))
|
||||||
|
emails.send_recover_failed(uid, token)
|
||||||
|
return redirect("/")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user