Source code for users.views

"""The views of the users app."""

from __future__ import annotations

from typing import TYPE_CHECKING

from django.contrib.auth.decorators import login_required
from django.contrib.messages import error, info
from django.db import IntegrityError
from django.db.models import Subquery
from django.http import Http404, HttpResponse
from django.shortcuts import render
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_control
from django.views.generic import FormView
from django.views.generic.base import TemplateView

from allauth.account.forms import UserTokenForm
from allauth.account.models import EmailAddress
from allauth.account.views import (
    LogoutView, PasswordResetFromKeyView, _ajax_response
)

from MangAdventure.jsonld import breadcrumbs

from reader.models import Chapter

from .forms import UserProfileForm
from .models import Bookmark, UserProfile

if TYPE_CHECKING:  # pragma: no cover
    from django.http import HttpRequest


[docs]@login_required @cache_control(private=True, max_age=3600) def profile(request: HttpRequest) -> HttpResponse: """ View that serves the profile page of a user. A :class:`UserProfile` will be created if it doesn't exist. It serves the logged in user's profile by default, but accepts an ``id`` query parameter to view an arbitrary user's profile. :param request: The original request. :return: A response with the rendered ``profile.html`` template. :raises Http404: If there is no active user with the specified ``id``. """ try: uid = int(request.GET.get('id', request.user.id)) # type: ignore prof = UserProfile.objects.select_related('user').only( 'avatar', 'bio', 'user__email', 'user__username', 'user__first_name', 'user__last_name', 'user__is_active', 'user__is_superuser' ).get_or_create(user_id=uid)[0] except (ValueError, IntegrityError) as e: raise Http404 from e if not prof.user.is_active: # pragma: no cover raise Http404('Inactive user') if uid != request.user.id and prof.user.is_superuser: raise Http404('Cannot view profile of superuser') uri = request.build_absolute_uri(request.path) crumbs = breadcrumbs([('User', uri)]) return render(request, 'profile.html', { 'profile': prof, 'breadcrumbs': crumbs })
[docs]@method_decorator(login_required, name='dispatch') @method_decorator(cache_control(no_store=True), name='dispatch') class EditUser(TemplateView): """View that serves the edit form for a user's profile.""" #: The template that this view will render. template_name = 'edit_user.html'
[docs] def setup(self, request: HttpRequest, *args, **kwargs): """ Initialize attributes shared by all view methods. A :class:`~users.models.UserProfile` will be created if the request user does not yet have one. :param request: The original request. """ super().setup(request) if request.user.is_authenticated: self.profile = UserProfile.objects.defer( 'token', 'user__last_login', 'user__is_staff', 'user__date_joined', 'user__is_active', 'user__is_superuser' ).select_related('user').get_or_create( user_id=request.user.id )[0] url = request.path p_url = url.rsplit('/', 2)[0] + '/' crumbs = breadcrumbs([ ('User', request.build_absolute_uri(p_url)), ('Edit', request.build_absolute_uri(url)) ]) self.extra_context = {'breadcrumbs': crumbs}
[docs] def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: """ Handle ``GET`` requests. :param request: The original request. :return: A response with the rendered :obj:`template <EditUser.template_name>`. """ form = UserProfileForm(instance=self.profile) return self.render_to_response(self.get_context_data(form=form))
[docs] def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: """ Handle ``POST`` requests. If the user has changed their e-mail, a confirmation mail will be sent. :param request: The original request. :return: A response with the rendered :obj:`template <EditUser.template_name>`. """ form = UserProfileForm( request.POST, request.FILES, instance=self.profile ) if form.is_valid(): form.save() email = form.cleaned_data['email'] if request.user.email != email: # type: ignore EmailAddress.objects.add_email( request, request.user, email, confirm=True ) info(request, 'Please confirm your new e-mail address.') else: error(request, 'Error: please check the fields and try again.') return self.render_to_response(self.get_context_data(form=form))
[docs]@method_decorator(login_required, name='dispatch') @method_decorator(cache_control(no_store=True), name='dispatch') class Logout(LogoutView): """A :class:`LogoutView` that disallows ``GET`` requests.""" #: The allowed HTTP methods. http_method_names = ('post', 'head', 'options')
[docs]@method_decorator(cache_control(no_store=True), name='dispatch') class PasswordReset(PasswordResetFromKeyView): """ A :class:`PasswordResetFromKeyView` without the extra redirect. .. seealso: `pennersr/django-allauth#2201`__ __ https://github.com/pennersr/django-allauth/issues/2201 """ # HACK: patch PasswordResetFromKeyView.dispatch to fix #27
[docs] def dispatch(self, request: HttpRequest, uidb36: str, key: str, **kwargs) -> HttpResponse: # pragma: no cover self.request = request self.key = key token_form = UserTokenForm(data={'uidb36': uidb36, 'key': self.key}) if token_form.is_valid(): self.reset_user = token_form.reset_user return super(FormView, self).dispatch( request, uidb36, self.key, **kwargs ) self.reset_user = None response = self.render_to_response( self.get_context_data(token_fail=True) ) return _ajax_response( self.request, response, form=token_form )
[docs]@method_decorator(login_required, name='dispatch') @method_decorator(cache_control(private=True, max_age=600), name='dispatch') class Bookmarks(TemplateView): """View that serves a user's bookmarks page.""" #: The template that this view will render. template_name = 'bookmarks.html'
[docs] def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: """ Handle ``GET`` requests. :param request: The original request. :return: A response with the rendered :obj:`template <EditUser.template_name>`. """ url = request.path p_url = url.rsplit('/', 2)[0] + '/' crumbs = breadcrumbs([ ('User', request.build_absolute_uri(p_url)), ('Bookmarks', request.build_absolute_uri(url)) ]) chapters = Chapter.objects.filter(series_id__in=Subquery( Bookmark.objects.filter(user_id=request.user.id).values('series') )).select_related('series').order_by('-published').only( 'title', 'volume', 'number', 'published', 'final', 'series__cover', 'series__slug', 'series__title', 'series__format' ) token = UserProfile.objects.only('token') \ .get_or_create(user_id=request.user.id)[0].token return self.render_to_response(self.get_context_data( releases=list(chapters), breadcrumbs=crumbs, token=token ))
[docs] def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: """ Handle ``POST`` requests. If a bookmark exists, it will be deleted. If not, it will be created. :param request: The original request. :return: | An empty :status:`201` response when creating a bookmark. | An empty :status:`204` response when deleting a bookmark. """ bookmark, created = Bookmark.objects.only('id').get_or_create( user_id=request.user.id, series_id=request.POST.get('series', 0) ) if not created: bookmark.delete() return HttpResponse(status=201 if created else 204)
__all__ = ['profile', 'EditUser', 'Bookmarks', 'Logout', 'PasswordReset']