hotpocket/services/backend/hotpocket_backend/apps/ui/views/accounts.py
Tomek Wójcik b4338e2769
Some checks failed
CI / Checks (push) Failing after 13m2s
Release v1.0.0
2025-08-20 21:00:50 +02:00

248 lines
7.2 KiB
Python

# -*- coding: utf-8 -*-
from __future__ import annotations
from django.contrib import messages
from django.contrib.auth import logout as auth_logout
from django.contrib.auth.views import LoginView as BaseLoginView
from django.core.exceptions import PermissionDenied
import django.db
from django.http import Http404, HttpRequest, HttpResponse
from django.shortcuts import redirect, render
from django.urls import reverse
from django.utils.http import url_has_allowed_host_and_scheme
from django.utils.translation import gettext_lazy as _
from django.views.generic import FormView, RedirectView
from hotpocket_backend.apps.accounts.decorators import account_required
from hotpocket_backend.apps.accounts.mixins import AccountRequiredMixin
from hotpocket_backend.apps.core.conf import settings as django_settings
from hotpocket_backend.apps.ui.forms.accounts import (
FederatedPasswordForm,
FederatedProfileForm,
LoginForm,
PasswordForm,
ProfileForm,
SettingsForm,
)
@account_required
def index(request: HttpRequest) -> HttpResponse:
return redirect(reverse('ui.accounts.settings'))
@account_required
def browse(request: HttpRequest) -> HttpResponse:
raise Http404()
class LoginView(BaseLoginView):
template_name = 'ui/accounts/login.html'
form_class = LoginForm
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
request.session['post_login_next_url'] = request.GET.get('next', None)
request.session.save()
return super().get(request, *args, **kwargs)
def get_success_url(self) -> str:
return reverse('ui.accounts.post_login')
class PostLoginView(RedirectView):
def get_redirect_url(self, *args, **kwargs) -> str:
next_url = self.request.session.pop('post_login_next_url', None)
self.request.session.save()
allowed_hosts = None
if len(django_settings.ALLOWED_HOSTS) > 0:
allowed_hosts = set(filter(
lambda value: value != '*',
django_settings.ALLOWED_HOSTS,
))
if next_url is not None:
next_url_is_safe = url_has_allowed_host_and_scheme(
url=next_url,
allowed_hosts=allowed_hosts,
require_https=self.request.is_secure(),
)
if next_url_is_safe is False:
next_url = None
return next_url or reverse('ui.index.index')
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
if request.user.is_anonymous is True:
raise PermissionDenied('NOPE')
return super().get(request, *args, **kwargs)
def logout(request: HttpRequest) -> HttpResponse:
if request.user.is_authenticated is True:
auth_logout(request)
return render(request, 'ui/accounts/logout.html')
@account_required
def settings(request: HttpRequest) -> HttpResponse:
return redirect(reverse('ui.accounts.settings.profile'))
class BaseSettingsView(AccountRequiredMixin, FormView):
template_name = 'ui/accounts/settings.html'
@property
def is_federated(self) -> bool:
return all((
self.request.user.is_anonymous is False,
self.request.user.has_usable_password() is False,
))
def get_context_data(self, **kwargs) -> dict:
result = super().get_context_data(**kwargs)
result.update({
'is_federated': self.is_federated,
})
return result
class ProfileView(BaseSettingsView):
def get_form_class(self) -> type[ProfileForm]:
if self.is_federated is True:
return FederatedProfileForm
return ProfileForm
def get_initial(self) -> dict:
result = super().get_initial()
result.update({
'username': self.request.user.username,
'first_name': self.request.user.first_name,
'last_name': self.request.user.last_name,
'email': self.request.user.email,
})
return result
def get_context_data(self, **kwargs) -> dict:
result = super().get_context_data(**kwargs)
result.update({
'title': _('Profile'),
'active_tab': 'profile',
})
return result
def form_valid(self, form: ProfileForm) -> HttpResponse:
assert self.is_federated is False, (
'Refuse to save profile of a federated account: '
f'account=`{self.request.user}`'
)
with django.db.transaction.atomic():
self.request.user.first_name = form.cleaned_data['first_name']
self.request.user.last_name = form.cleaned_data['last_name']
self.request.user.email = form.cleaned_data['email']
self.request.user.save()
messages.add_message(
self.request,
messages.SUCCESS,
message=_('Your profile has been been updated!'),
)
return super().form_valid(form)
def get_success_url(self) -> str:
return reverse('ui.accounts.settings.profile')
class PasswordView(BaseSettingsView):
def get_form_class(self) -> type[PasswordForm]:
if self.is_federated is True:
return FederatedPasswordForm
return PasswordForm
def get_form(self,
form_class: type[PasswordForm] | None = None,
) -> PasswordForm:
form_class = form_class or self.get_form_class()
return form_class(self.request.user, **self.get_form_kwargs())
def get_context_data(self, **kwargs) -> dict:
result = super().get_context_data(**kwargs)
result.update({
'title': _('Password'),
'active_tab': 'password',
})
return result
def form_valid(self, form: PasswordForm) -> HttpResponse:
assert self.is_federated is False, (
'Refuse to change password of a federated account: '
f'account=`{self.request.user}`'
)
with django.db.transaction.atomic():
form.set_password_and_save(
self.request.user,
password_field_name='new_password1',
)
messages.add_message(
self.request,
messages.SUCCESS,
message=_('Your password has been changed!'),
)
return super().form_valid(form)
def get_success_url(self) -> str:
return reverse('ui.accounts.settings.password')
class SettingsView(BaseSettingsView):
form_class = SettingsForm
def get_context_data(self, **kwargs) -> dict:
result = super().get_context_data(**kwargs)
result.update({
'title': _('Settings'),
'active_tab': 'settings',
'is_federated': False,
})
return result
def get_initial(self) -> dict:
return self.request.user.settings
def form_valid(self, form: PasswordForm) -> HttpResponse:
with django.db.transaction.atomic():
self.request.user.raw_settings = form.cleaned_data
self.request.user.save()
messages.add_message(
self.request,
messages.SUCCESS,
message=_('Your settings have been updated!'),
)
return super().form_valid(form)
def get_success_url(self) -> str:
return reverse('ui.accounts.settings.settings')