Coverage for website/thaliawebsite/views.py: 59.72%
62 statements
« prev ^ index » next coverage.py v7.6.7, created at 2025-08-14 10:31 +0000
« prev ^ index » next coverage.py v7.6.7, created at 2025-08-14 10:31 +0000
1"""General views for the website."""
3from django.contrib import messages
4from django.contrib.admin.views.decorators import staff_member_required
5from django.contrib.auth.views import LogoutView as BaseLogoutView
6from django.contrib.auth.views import PasswordResetView
7from django.core.exceptions import PermissionDenied
8from django.http import HttpResponse, HttpResponseForbidden
9from django.shortcuts import redirect
10from django.utils.decorators import method_decorator
11from django.views.generic import ListView, TemplateView
12from django.views.generic.base import View
14from django_otp import user_has_device
15from django_ratelimit.decorators import ratelimit
16from two_factor.views import LoginView
19class IndexView(TemplateView):
20 template_name = "index.html"
23@method_decorator(staff_member_required, "dispatch")
24class TestCrashView(View):
25 """Test view to intentionally crash to test the error handling."""
27 def dispatch(self, request, *args, **kwargs) -> HttpResponse:
28 if not request.user.is_superuser:
29 return HttpResponseForbidden("This is not for you")
30 raise Exception("Test exception")
33class PagedView(ListView):
34 """A ListView with automatic pagination."""
36 def get_context_data(self, **kwargs) -> dict:
37 context = super().get_context_data(**kwargs)
38 page = context["page_obj"].number
39 paginator = context["paginator"]
41 # Show the two pages before and after the current page
42 page_range_start = max(1, page - 2)
43 page_range_stop = min(page + 3, paginator.num_pages + 1)
45 # Add extra pages if we show less than 5 pages
46 page_range_start = min(page_range_start, page_range_stop - 5)
47 page_range_start = max(1, page_range_start)
49 # Add extra pages if we still show less than 5 pages
50 page_range_stop = max(page_range_stop, page_range_start + 5)
51 page_range_stop = min(page_range_stop, paginator.num_pages + 1)
53 page_range = range(page_range_start, page_range_stop)
55 context.update(
56 {
57 "page_range": page_range,
58 }
59 )
61 return context
64class RateLimitedPasswordResetView(PasswordResetView):
65 @method_decorator(ratelimit(key="ip", rate="5/m"))
66 def post(self, request, *args, **kwargs):
67 return super().post(request, *args, **kwargs)
70class RateLimitedLoginView(LoginView):
71 @method_decorator(ratelimit(key="ip", rate="50/m"))
72 def post(self, request, *args, **kwargs):
73 return super().post(request, *args, **kwargs)
76class LogoutView(BaseLogoutView):
77 # Allow GET logout still (this was deprecated in Django 5.0).
78 http_method_names = ["get", "post", "options"]
80 def get(self, request, *args, **kwargs):
81 return self.post(request, *args, **kwargs)
84def rate_limited_view(request, *args, **kwargs):
85 return HttpResponse("You are rate limited", status=429)
88def admin_unauthorized_view(request):
89 if not request.member:
90 url = "/user/account/login"
91 args = request.META.get("QUERY_STRING", "")
92 if args:
93 url = f"{url}?{args}"
94 return redirect(url)
95 elif not request.member.is_staff and not request.member.is_superuser:
96 raise PermissionDenied("You are not allowed to access the administration page.")
97 elif not user_has_device(request.member):
98 messages.error(
99 request,
100 "You need to set up two-factor authentication to access the administration page.",
101 )
102 return redirect("two_factor:setup")
103 else:
104 return redirect(request.GET.get("next", "/"))