From 611a1017ed52fb7205065759ebac0c58fa6e095d Mon Sep 17 00:00:00 2001 From: Dorian Lesbre <dorian.lesbre@gmail.com> Date: Sat, 6 Mar 2021 11:12:16 +0100 Subject: [PATCH] Added change password options --- accounts/forms.py | 66 ++++++++++++++++++++++ accounts/templates/activation_email.html | 3 +- accounts/templates/change_email.html | 7 +++ accounts/templates/profile.html | 2 - accounts/templates/update.html | 11 +++- accounts/urls.py | 1 + accounts/views.py | 72 ++++++++++++++++++------ 7 files changed, 139 insertions(+), 23 deletions(-) create mode 100644 accounts/templates/change_email.html diff --git a/accounts/forms.py b/accounts/forms.py index 345e9c9..c38d990 100644 --- a/accounts/forms.py +++ b/accounts/forms.py @@ -1,9 +1,24 @@ from django import forms +from django.contrib.auth import authenticate, password_validation from django.contrib.auth.forms import UserCreationForm from django.utils.safestring import mark_safe from accounts.models import EmailUser +def password_criterions_html(): + """Wraps password criterions into nice html used by other forms""" + def wrap_str(s, tagopen, tagclose=None): + if not tagclose: + tagclose = tagopen + return "<{}>{}</{}>".format(tagopen, s, tagclose) + + criterions = password_validation.password_validators_help_texts() + criterions_html = wrap_str( + "\n".join(map(lambda crit: wrap_str(crit, "li"), criterions)), + 'ul class="helptext"', + "ul", + ) + return mark_safe(criterions_html) class FormRenderMixin: """ A mixin that can be included in any form to make it render to html as we want @@ -163,6 +178,57 @@ class UpdateAccountForm(FormRenderMixin, forms.ModelForm): user.username = email if email_changed: user.email_confirmed = False + user.is_active = False if commit: user.save() return user + +class UpdatePasswordForm(FormRenderMixin, forms.Form): + """ Form to update one's password """ + + current_password = forms.CharField( + widget=forms.PasswordInput, label="Mot de passe actuel", + ) + password = forms.CharField( + widget=forms.PasswordInput, + help_text=password_criterions_html(), + label="Nouveau mot de passe", + ) + password_confirm = forms.CharField( + widget=forms.PasswordInput, label="Nouveau mot de passe (confirmation)", + ) + + def __init__(self, *args, **kwargs): + self.user = kwargs.pop("user", None) + super().__init__(*args, **kwargs) + + def clean_current_password(self): + """ Check current password correctness """ + cur_password = self.cleaned_data["current_password"] + if authenticate(username=self.user.email, password=cur_password) != self.user: + raise forms.ValidationError("Votre mot de passe actuel est incorrect.") + return cur_password + + def clean_password(self): + """ Check password strength """ + password = self.cleaned_data["password"] + password_validation.validate_password(password) + return password + + def clean_password_confirm(self): + """ Check that both passwords match """ + cleaned_data = super().clean() + password = cleaned_data.get("password") + password_confirm = cleaned_data.get("password_confirm") + if not password: + return None + if password != password_confirm: + raise forms.ValidationError( + "Les deux mots de passe ne sont pas identiques." + ) + return cleaned_data + + def apply(self): + """ Apply the password change, assuming validation was already passed """ + self.user.set_password(self.cleaned_data["password"]) + self.user.save() diff --git a/accounts/templates/activation_email.html b/accounts/templates/activation_email.html index ec2839b..16b2d3a 100644 --- a/accounts/templates/activation_email.html +++ b/accounts/templates/activation_email.html @@ -1,8 +1,7 @@ - {% autoescape off %} Bonjour {{ user.first_name }} {{ user.last_name }}, -Veuillez suivre le lien ci dessous pour valider votre compte : +Veuillez suivre le lien ci-dessous pour valider votre compte : http://{{ domain }}{% url 'accounts:activate' uidb64=uid token=token %} {% endautoescape %} diff --git a/accounts/templates/change_email.html b/accounts/templates/change_email.html new file mode 100644 index 0000000..1e2a2b0 --- /dev/null +++ b/accounts/templates/change_email.html @@ -0,0 +1,7 @@ +{% autoescape off %} +Bonjour {{ user.first_name }} {{ user.last_name }}, + +Veuillez suivre le lien ci dessous pour valider le changement d'adresse email : + +http://{{ domain }}{% url 'accounts:activate' uidb64=uid token=token %} +{% endautoescape %} diff --git a/accounts/templates/profile.html b/accounts/templates/profile.html index c2bfd73..892408d 100644 --- a/accounts/templates/profile.html +++ b/accounts/templates/profile.html @@ -16,8 +16,6 @@ <p><a href="{% url 'accounts:update' %}">Modifier mes informations</a></p> -<p><a href="TODO">Changer mom mot de passe</a></p> - <p><a class="button" href="{% url 'accounts:logout' %}">Déconnexion</a></p> {% endblock %} \ No newline at end of file diff --git a/accounts/templates/update.html b/accounts/templates/update.html index d3c41e8..d13b928 100644 --- a/accounts/templates/update.html +++ b/accounts/templates/update.html @@ -5,7 +5,16 @@ <form method="post" action="{% url 'accounts:update' %}"> {% csrf_token %} - {{ form.as_html }} + {{ update_form.as_html }} + <br> + <input type="submit" value="Valider"> + </form> + + <h2>Changer mon mot de passe</h2> + + <form method="post" action="{% url 'accounts:change_password' %}"> + {% csrf_token %} + {{ password_form.as_html }} <br> <input type="submit" value="Valider"> </form> diff --git a/accounts/urls.py b/accounts/urls.py index c59f5fc..fd0184a 100644 --- a/accounts/urls.py +++ b/accounts/urls.py @@ -10,5 +10,6 @@ urlpatterns = [ path("profile/", views.ProfileView.as_view(), name="profile"), path("create/", views.CreateAccountView.as_view(), name="create"), path("update/", views.UpdateAccountView.as_view(), name="update"), + path("change_password/", views.UpdatePasswordView.as_view(), name="change_password"), path('activate/<uidb64>/<token>/', views.ActivateAccountView.as_view(), name='activate'), ] diff --git a/accounts/views.py b/accounts/views.py index 48c5499..2ec7694 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -8,14 +8,26 @@ from django.utils.encoding import force_bytes from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode from django.urls import reverse from django.template.loader import render_to_string -from django.views.generic import RedirectView, TemplateView, UpdateView, View +from django.views.generic import FormView, RedirectView, TemplateView, UpdateView, View from django.shortcuts import render, redirect -from accounts.forms import CreateAccountForm, UpdateAccountForm +from accounts.forms import CreateAccountForm, UpdateAccountForm, UpdatePasswordForm from accounts.models import EmailUser from accounts.tokens import email_token_generator from site_settings.models import SiteSettings +def send_validation_email(request, user, subject, template): + """Send a validation email to user""" + current_site = get_current_site(request) + message = render_to_string(template, { + 'user': user, + 'domain': current_site.domain, + 'uid': urlsafe_base64_encode(force_bytes(user.pk)), + 'token': email_token_generator.make_token(user), + }) + user.email_user(subject, message) + + class LoginView(DjangoLoginView): """Vue pour se connecter""" template_name = "login.html" @@ -71,15 +83,7 @@ class CreateAccountView(View): user.email_confirmed = False user.save() - current_site = get_current_site(request) - subject = 'Activation de votre compte Interludes' - message = render_to_string('activation_email.html', { - 'user': user, - 'domain': current_site.domain, - 'uid': urlsafe_base64_encode(force_bytes(user.pk)), - 'token': email_token_generator.make_token(user), - }) - user.email_user(subject, message) + send_validation_email(request, user, "Activer votre compte Interludes", "activation_email.html") messages.info(request, 'Un lien vous a été envoyé par mail. Utilisez le pour finaliser la création de compte.') @@ -127,18 +131,50 @@ class UpdateAccountView(LoginRequiredMixin, UpdateView): def get_object(self): return self.request.user - # def get_context_data(self, **kwargs): - # context = super().get_context_data(**kwargs) - # context["change_password_form"] = registration_forms.UpdatePasswordForm( - # user=self.request.user - # ) - # return context + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["update_form"] = context["form"] + context["password_form"] = UpdatePasswordForm( + user=self.request.user + ) + return context def get_success_url(self): - # if not self.request.user.email_confirmed: + if not self.request.user.email_confirmed: + send_validation_email( + self.request, self.request.user, + "Valider le changement d'email de votre compte Interludes", + "change_email.html" + ) + + messages.info(self.request, 'Un lien vous a été envoyé par mail. Utilisez le pour valider la mise à jour.') + # return reverse("registration:email_confirmation_needed") return reverse("accounts:profile") def form_valid(self, form): messages.success(self.request, "Informations personnelles mises à jour") return super().form_valid(form) + + +class UpdatePasswordView(LoginRequiredMixin, FormView): + """ Change a user's password """ + + template_name = "update.html" + form_class = UpdatePasswordForm + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["password_form"] = context["form"] + context["update_form"] = UpdateAccountForm(instance=self.request.user) + return context + + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + kwargs["user"] = self.request.user + return kwargs + + def form_valid(self, form): + form.apply() + messages.success(self.request, "Mot de passe mis à jour") + return redirect("accounts:profile") -- GitLab