mybuddy/babybuddy/views.py

239 lines
7.9 KiB
Python

# -*- coding: utf-8 -*-
from xmlrpc.client import Boolean
from django.contrib import messages
from django.contrib.auth import update_session_auth_hash
from django.contrib.auth.forms import PasswordChangeForm
from django.contrib.auth.models import User
from django.contrib.auth.views import LogoutView as LogoutViewBase
from django.contrib.messages.views import SuccessMessageMixin
from django.core.exceptions import BadRequest
from django.http import HttpResponseForbidden
from django.middleware.csrf import REASON_BAD_ORIGIN
from django.shortcuts import redirect, render
from django.template import loader
from django.urls import reverse, reverse_lazy
from django.utils import translation
from django.utils.decorators import method_decorator
from django.utils.text import format_lazy
from django.utils.translation import gettext as _, gettext_lazy
from django.views import csrf
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect
from django.views.decorators.http import require_POST
from django.views.generic import View
from django.views.generic.base import TemplateView, RedirectView
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.views.i18n import set_language
from django_filters.views import FilterView
from babybuddy import forms
from babybuddy.mixins import LoginRequiredMixin, PermissionRequiredMixin, StaffOnlyMixin
def csrf_failure(request, reason=""):
"""
Overrides the 403 CSRF failure template for bad origins in order to provide more
userful information about how to resolve the issue.
"""
if (
"HTTP_ORIGIN" in request.META
and reason == REASON_BAD_ORIGIN % request.META["HTTP_ORIGIN"]
):
context = {
"title": _("Forbidden"),
"main": _("CSRF verification failed. Request aborted."),
"reason": reason,
"origin": request.META["HTTP_ORIGIN"],
}
template = loader.get_template("error/403_csrf_bad_origin.html")
return HttpResponseForbidden(template.render(context), content_type="text/html")
return csrf.csrf_failure(request, reason, "403_csrf.html")
class RootRouter(LoginRequiredMixin, RedirectView):
"""
Redirects to the site dashboard.
"""
def get_redirect_url(self, *args, **kwargs):
self.url = reverse("dashboard:dashboard")
return super(RootRouter, self).get_redirect_url(self, *args, **kwargs)
class BabyBuddyFilterView(FilterView):
"""
Disables "strictness" for django-filter. It is unclear from the
documentation exactly what this does...
"""
# TODO Figure out the correct way to use this.
strict = False
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
children = {o.child for o in context["object_list"] if hasattr(o, "child")}
if len(children) == 1:
context["unique_child"] = True
return context
@method_decorator(csrf_protect, name="dispatch")
@method_decorator(never_cache, name="dispatch")
@method_decorator(require_POST, name="dispatch")
class LogoutView(LogoutViewBase):
pass
class UserList(StaffOnlyMixin, BabyBuddyFilterView):
model = User
template_name = "babybuddy/user_list.html"
ordering = "username"
paginate_by = 10
filterset_fields = ("username", "first_name", "last_name", "email")
class UserAdd(StaffOnlyMixin, PermissionRequiredMixin, SuccessMessageMixin, CreateView):
model = User
template_name = "babybuddy/user_form.html"
permission_required = ("admin.add_user",)
form_class = forms.UserAddForm
success_url = reverse_lazy("babybuddy:user-list")
success_message = gettext_lazy("User %(username)s added!")
class UserUpdate(
StaffOnlyMixin, PermissionRequiredMixin, SuccessMessageMixin, UpdateView
):
model = User
template_name = "babybuddy/user_form.html"
permission_required = ("admin.change_user",)
form_class = forms.UserUpdateForm
success_url = reverse_lazy("babybuddy:user-list")
success_message = gettext_lazy("User %(username)s updated.")
class UserDelete(
StaffOnlyMixin, PermissionRequiredMixin, DeleteView, SuccessMessageMixin
):
model = User
template_name = "babybuddy/user_confirm_delete.html"
permission_required = ("admin.delete_user",)
success_url = reverse_lazy("babybuddy:user-list")
def get_success_message(self, cleaned_data):
return format_lazy(gettext_lazy("User {user} deleted."), user=self.get_object())
class UserPassword(LoginRequiredMixin, View):
"""
Handles user password changes.
"""
form_class = forms.UserPasswordForm
template_name = "babybuddy/user_password_form.html"
def get(self, request):
return render(
request, self.template_name, {"form": self.form_class(request.user)}
)
def post(self, request):
form = PasswordChangeForm(request.user, request.POST)
if form.is_valid():
user = form.save()
update_session_auth_hash(request, user)
messages.success(request, _("Password updated."))
return render(request, self.template_name, {"form": form})
class RegenerateApiKey:
def handle_api_key_post(self, request) -> Boolean:
if request.POST.get("api_key_regenerate"):
request.user.settings.api_key(reset=True)
messages.success(request, _("User API key regenerated."))
return True
return False
class UserSettings(LoginRequiredMixin, RegenerateApiKey, View):
"""
Handles both the User and Settings models.
Based on this SO answer: https://stackoverflow.com/a/45056835.
"""
form_user_class = forms.UserForm
form_settings_class = forms.UserSettingsForm
template_name = "babybuddy/user_settings_form.html"
def get(self, request):
settings = request.user.settings
return render(
request,
self.template_name,
{
"form_user": self.form_user_class(instance=request.user),
"form_settings": self.form_settings_class(instance=settings),
},
)
def post(self, request):
if self.handle_api_key_post(request):
return redirect("babybuddy:user-settings")
form_user = self.form_user_class(instance=request.user, data=request.POST)
form_settings = self.form_settings_class(
instance=request.user.settings, data=request.POST
)
if form_user.is_valid() and form_settings.is_valid():
user = form_user.save(commit=False)
user_settings = form_settings.save(commit=False)
user.settings = user_settings
user.save()
translation.activate(user.settings.language)
messages.success(request, _("Settings saved!"))
translation.deactivate()
return set_language(request)
return render(
request,
self.template_name,
{"user_form": form_user, "settings_form": form_settings},
)
class UserAddDevice(LoginRequiredMixin, RegenerateApiKey, View):
form_user_class = forms.UserForm
template_name = "babybuddy/user_add_device.html"
qr_code_template = "babybuddy/login_qr_code.txt"
def get(self, request):
qr_code_response = render(request, self.qr_code_template)
qr_code_data = qr_code_response.content.decode().strip()
return render(
request,
self.template_name,
{
"form_user": self.form_user_class(instance=request.user),
"qr_code_data": qr_code_data,
},
)
def post(self, request):
if self.handle_api_key_post(request):
return redirect("babybuddy:user-add-device")
else:
raise BadRequest()
class Welcome(LoginRequiredMixin, TemplateView):
"""
Basic introduction to Baby Buddy (meant to be shown when no data is in the
database).
"""
template_name = "babybuddy/welcome.html"