# -*- 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')