From f31771b5882af0468ae077ebb8246eead0895fae Mon Sep 17 00:00:00 2001 From: Dirk Alders Date: Sun, 27 Oct 2024 19:04:17 +0100 Subject: [PATCH] Password and User settings Form implemented --- forms.py | 72 +++++++++++++++++- migrations/0004_userprofile_mail_pending.py | 18 +++++ models.py | 1 + templates/users/profile.html | 9 ++- templates/users/profile_formdata.html | 6 -- views.py | 81 +++++++++++---------- 6 files changed, 138 insertions(+), 49 deletions(-) create mode 100644 migrations/0004_userprofile_mail_pending.py delete mode 100644 templates/users/profile_formdata.html diff --git a/forms.py b/forms.py index 8c45568..181e2d1 100644 --- a/forms.py +++ b/forms.py @@ -1,7 +1,9 @@ from django import forms from django.contrib.auth.models import User -from django.contrib.auth.forms import UserCreationForm, UserChangeForm -from .models import UserProfile +from django.contrib.auth.forms import UserCreationForm, UserChangeForm, PasswordChangeForm +from django.utils.translation import gettext as _ +from .models import UserProfile, get_userprofile +from users import emails class UserRegistrationForm(UserCreationForm): @@ -24,6 +26,72 @@ class UserProfileFormLanguageOnly(forms.ModelForm): fields = ['language_code'] +class UserPasswordChangeForm(PasswordChangeForm): + email = forms.EmailField() + first_name = forms.CharField(max_length=150) + last_name = forms.CharField(max_length=150) + field_order = ["email", "first_name", "last_name", "old_password", "new_password1", "new_password2"] + + def __init__(self, request): + self.request = request + # + data = request.POST or {'email': request.user.email, 'first_name': request.user.first_name, 'last_name': request.user.last_name} + # + super().__init__(request.user, data) + self.fields['old_password'].widget.attrs.update({'autofocus': False}) + self.fields['old_password'].required = False + + def clean_old_password(self): + """ + Validation of old_password field only, if set. + """ + old_password = self.cleaned_data["old_password"] + if old_password and not self.user.check_password(old_password): + raise forms.ValidationError( + self.error_messages["password_incorrect"], + code="password_incorrect", + ) + return old_password + + def clean(self): + """ + Validation of new_passwords only if old_password field is set. + """ + clean_data = forms.Form.clean(self) + old_password = clean_data.get('old_password') + # + if old_password: + self.validate_passwords("new_password1", "new_password2") + self.validate_password_for_user(self.user, "new_password2") + return forms.Form.clean(self) + + def save(self, commit=True): + changed = False + for key in self.fields: + new = self.data.get(key) + try: + old = getattr(self.user, key) + except AttributeError: + pass # Is a password field + else: + if new != old: + if key == 'email': + emails.send_validation_mail(self.user, self.request) + up = get_userprofile(self.user) + up.mail_pending = new + up.save() + else: + changed = True + setattr(self.user, key, new) + if self.data.get('new_password1'): + changed = True + self.user.set_password(self.data.get('new_password1')) + # + if changed: + self.user.save() + return changed + + class UserActivationForm(UserChangeForm): password = None diff --git a/migrations/0004_userprofile_mail_pending.py b/migrations/0004_userprofile_mail_pending.py new file mode 100644 index 0000000..f83fcc9 --- /dev/null +++ b/migrations/0004_userprofile_mail_pending.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.2 on 2024-10-27 17:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0003_userprofile_mail_validated'), + ] + + operations = [ + migrations.AddField( + model_name='userprofile', + name='mail_pending', + field=models.EmailField(max_length=254, null=True), + ), + ] diff --git a/models.py b/models.py index 0ddcaf0..0d17d45 100644 --- a/models.py +++ b/models.py @@ -30,6 +30,7 @@ class UserProfile(models.Model): timezone = models.CharField(max_length=150, default='UTC', choices=[(t, t) for t in pytz.common_timezones]) language_code = models.CharField(max_length=150, default='en', choices=settings.LANGUAGES) mail_validated = models.BooleanField(default=False) + mail_pending = models.EmailField(null=True) def export_key(self): return self.user.username diff --git a/templates/users/profile.html b/templates/users/profile.html index b4cd62b..f91cb87 100644 --- a/templates/users/profile.html +++ b/templates/users/profile.html @@ -4,7 +4,14 @@ {% block content %}
{% csrf_token %} - {% include 'users/profile_formdata.html' %} + + {% get_current_language as LANGUAGE_CODE %} +

{% trans "Language and Timezone" %}

+ {{ form_userprofile.as_p }} + +

{% trans "Userdata and Password" %}

+ {{ form_userchange.as_p }} +
diff --git a/templates/users/profile_formdata.html b/templates/users/profile_formdata.html deleted file mode 100644 index 3dd2aab..0000000 --- a/templates/users/profile_formdata.html +++ /dev/null @@ -1,6 +0,0 @@ -{% load i18n %} - -{% get_current_language as LANGUAGE_CODE %} -

{% trans "Language and Timezone" %}

- {{ form_userprofile.as_p }} - diff --git a/views.py b/views.py index 0ec0f0e..b4f00fa 100644 --- a/views.py +++ b/views.py @@ -11,7 +11,7 @@ from django.contrib.auth.models import User from django.utils.encoding import force_str from django.utils.http import urlsafe_base64_decode from django.utils.translation import gettext as _ -from .forms import UserRegistrationForm, UserProfileForm, UserActivationForm +from .forms import UserRegistrationForm, UserProfileForm, UserActivationForm, UserPasswordChangeForm import logging from .models import get_userprofile from themes import Context @@ -28,37 +28,28 @@ def password_recovery(request): return redirect(request.GET.get('next') or '/') -def profile_post_actions(request, context): - if request.POST: - form = context.get('form_userprofile') - if form.is_valid(): - form.save() - return redirect(request.GET.get('next') or '/') - - -def profile_pre_actions(request, context, form_to_be_used=UserProfileForm): - profile = get_userprofile(request.user) - if request.POST: - form = form_to_be_used(request.POST, instance=profile) - else: - form = form_to_be_used(instance=profile) - context['form_userprofile'] = form - - @login_required def profile(request): context = Context(request) # needs to be executed first because of time mesurement - profile_pre_actions(request, context) - response = profile_post_actions(request, context) - if response is not None: - return response + profile = get_userprofile(request.user) + if request.POST: + form_userprofile = UserProfileForm(request.POST, instance=profile) + form_userchange = UserPasswordChangeForm(request) + if form_userprofile.is_valid() and form_userchange.is_valid(): + form_userprofile.save() + form_userchange.save() + return redirect(request.GET.get('next') or '/') else: - context_adaption( - context, - request, - _('Profile for %(username)s') % {'username': request.user.username}, - ) - return render(request, 'users/profile.html', context=context) + form_userprofile = UserProfileForm(instance=profile) + form_userchange = UserPasswordChangeForm(request) + context_adaption( + context, + request, + _('Profile for %(username)s') % {'username': request.user.username}, + form_userprofile=form_userprofile, + form_userchange=form_userchange, + ) + return render(request, 'users/profile.html', context=context) def register(request): @@ -158,20 +149,30 @@ def validate(request, uidb64, token): myuser = None if myuser is not None and generate_token.check_token(myuser, token): - # Store mail validation to user profile - profile = get_userprofile(myuser) - profile.mail_validated = True - profile.save() - if not parameter.get(parameter.USERS_ADMIN_ACTIVATION): - # Activate user - myuser.is_active = True + up = get_userprofile(myuser) + if up.mail_pending: + # change of email-address + myuser.email = up.mail_pending myuser.save() - messages.success(request, _("Your Account has been activated.")) - return redirect('users-login') - else: - emails.send_activation_mail(myuser, request) - messages.success(request, _("Your Email has been validated. Wait for the administrator to activate your account")) + up.mail_pending = None + up.save() + messages.success(request, _("Your new email address is now active.")) return redirect("/") + else: + # Store mail validation to user profile + profile = get_userprofile(myuser) + profile.mail_validated = True + profile.save() + if not parameter.get(parameter.USERS_ADMIN_ACTIVATION): + # Activate user + myuser.is_active = True + myuser.save() + messages.success(request, _("Your Account has been activated.")) + return redirect('users-login') + else: + emails.send_activation_mail(myuser, request) + messages.success(request, _("Your Email has been validated. Wait for the administrator to activate your account")) + return redirect("/") else: context_adaption( context,