Accounts: add password change form to settings
All checks were successful
Build and push Docker image / build (push) Successful in 57s
Test / test (push) Successful in 15s

Uses Django's built-in PasswordChangeForm and update_session_auth_hash
so the session stays valid after the change. Form is hidden in a
<details> element and opens automatically on validation errors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
marwin 2026-05-25 16:26:07 +02:00
parent c064d0d4e1
commit e3a9c61c05
3 changed files with 43 additions and 3 deletions

View file

@ -13,4 +13,5 @@ urlpatterns = [
path('background/upload/', views.upload_background, name='upload_background'),
path('background/delete/', views.delete_background, name='delete_background'),
path('focus-station/', views.save_focus_station, name='save_focus_station'),
path('change-password/', views.change_password, name='change_password'),
]

View file

@ -1,9 +1,9 @@
import base64
import json
from django.conf import settings
from django.contrib.auth import authenticate, login, get_user_model
from django.contrib.auth import authenticate, login, get_user_model, update_session_auth_hash
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm, PasswordChangeForm
from django.http import JsonResponse
from django.shortcuts import render, redirect
from django.views.decorators.csrf import csrf_exempt
@ -71,6 +71,7 @@ def settings_view(request):
context = {
'profile': profile,
'has_lastfm': profile.has_lastfm(),
'password_form': PasswordChangeForm(request.user),
}
return render(request, 'accounts/settings.html', context)
@ -181,6 +182,23 @@ def save_focus_station(request):
return JsonResponse({'ok': True})
@login_required
@require_http_methods(['POST'])
def change_password(request):
form = PasswordChangeForm(request.user, request.POST)
if form.is_valid():
user = form.save()
update_session_auth_hash(request, user)
return redirect('settings')
profile = request.user.profile
return render(request, 'accounts/settings.html', {
'profile': profile,
'has_lastfm': profile.has_lastfm(),
'password_form': form,
'password_form_open': True,
})
@login_required
@require_http_methods(['POST'])
def lastfm_disconnect(request):

View file

@ -72,7 +72,28 @@
<section class="settings-section">
<h2>Account</h2>
<p>Logged in as <strong>{{ request.user.username }}</strong></p>
<form method="post" action="{% url 'logout' %}" class="inline-form">
<details {% if password_form_open %}open{% endif %} style="margin-top:1rem;">
<summary class="btn" style="display:inline-block;cursor:pointer;">Change password</summary>
<form method="post" action="{% url 'change_password' %}" style="margin-top:1rem; display:flex; flex-direction:column; gap:0.6rem; max-width:320px;">
{% csrf_token %}
{% for field in password_form %}
<div>
<label style="display:block; font-size:0.85rem; margin-bottom:2px;">{{ field.label }}</label>
{{ field }}
{% if field.errors %}
<div class="message message-error" style="margin-top:4px; padding:4px 8px; font-size:0.8rem;">{{ field.errors|join:", " }}</div>
{% endif %}
</div>
{% endfor %}
{% if password_form.non_field_errors %}
<div class="message message-error" style="padding:4px 8px; font-size:0.8rem;">{{ password_form.non_field_errors|join:", " }}</div>
{% endif %}
<div><button type="submit" class="btn">Save</button></div>
</form>
</details>
<form method="post" action="{% url 'logout' %}" class="inline-form" style="margin-top:1rem;">
{% csrf_token %}
<button type="submit" class="btn">Logout</button>
</form>