Sith/core/views/__init__.py

344 lines
10 KiB
Python
Raw Normal View History

#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
# - Sli <antoine@bartuccio.fr>
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
#
#
2016-12-14 17:05:19 +00:00
import types
2024-07-12 07:34:16 +00:00
from typing import Any
from django.conf import settings
2024-07-18 18:23:30 +00:00
from django.contrib.auth.mixins import AccessMixin
2024-06-24 11:07:36 +00:00
from django.core.exceptions import (
ImproperlyConfigured,
PermissionDenied,
)
from django.http import (
HttpResponseForbidden,
HttpResponseNotFound,
HttpResponseServerError,
)
from django.shortcuts import render
2024-06-24 11:07:36 +00:00
from django.utils.functional import cached_property
from django.views.generic.base import View
2019-04-10 11:21:34 +00:00
from django.views.generic.detail import SingleObjectMixin
2024-06-24 11:07:36 +00:00
from django.views.generic.edit import FormView
from sentry_sdk import last_event_id
2024-07-12 07:34:16 +00:00
from core.models import User
2016-08-31 00:43:49 +00:00
from core.views.forms import LoginForm
2018-10-04 19:29:19 +00:00
def forbidden(request, exception):
2024-06-27 12:46:43 +00:00
context = {"next": request.path, "form": LoginForm()}
if popup := request.resolver_match.kwargs.get("popup"):
context["popup"] = popup
return HttpResponseForbidden(render(request, "core/403.jinja", context=context))
2018-10-04 19:29:19 +00:00
def not_found(request, exception):
return HttpResponseNotFound(
render(request, "core/404.jinja", context={"exception": exception})
)
2018-10-04 19:29:19 +00:00
def internal_servor_error(request):
request.sentry_last_event_id = last_event_id
return HttpResponseServerError(render(request, "core/500.jinja"))
2024-07-12 07:34:16 +00:00
def can_edit_prop(obj: Any, user: User) -> bool:
"""Can the user edit the properties of the object.
2024-07-12 07:34:16 +00:00
Args:
obj: Object to test for permission
user: core.models.User to test permissions against
2024-07-12 07:34:16 +00:00
Returns:
True if user is authorized to edit object properties else False
2024-07-12 07:34:16 +00:00
Examples:
```python
if not can_edit_prop(self.object ,request.user):
raise PermissionDenied
2024-07-12 07:34:16 +00:00
```
"""
return obj is None or user.is_owner(obj)
2016-02-05 15:59:42 +00:00
2018-10-04 19:29:19 +00:00
def can_edit(obj: Any, user: User) -> bool:
2024-07-12 07:34:16 +00:00
"""Can the user edit the object.
2024-07-12 07:34:16 +00:00
Args:
obj: Object to test for permission
user: core.models.User to test permissions against
2024-07-12 07:34:16 +00:00
Returns:
True if user is authorized to edit object else False
2024-07-12 07:34:16 +00:00
Examples:
```python
if not can_edit(self.object, request.user):
raise PermissionDenied
2024-07-12 07:34:16 +00:00
```
"""
2016-02-05 15:59:42 +00:00
if obj is None or user.can_edit(obj):
return True
return can_edit_prop(obj, user)
2018-10-04 19:29:19 +00:00
def can_view(obj: Any, user: User) -> bool:
2024-07-12 07:34:16 +00:00
"""Can the user see the object.
2024-07-12 07:34:16 +00:00
Args:
obj: Object to test for permission
user: core.models.User to test permissions against
2024-07-12 07:34:16 +00:00
Returns:
True if user is authorized to see object else False
2024-07-12 07:34:16 +00:00
Examples:
```python
if not can_view(self.object ,request.user):
raise PermissionDenied
2024-07-12 07:34:16 +00:00
```
"""
2016-02-05 15:59:42 +00:00
if obj is None or user.can_view(obj):
return True
return can_edit(obj, user)
2018-10-04 19:29:19 +00:00
class GenericContentPermissionMixinBuilder(View):
2024-07-12 07:34:16 +00:00
"""Used to build permission mixins.
2019-10-17 09:24:51 +00:00
2024-07-12 07:34:16 +00:00
This view protect any child view that would be showing an object that is restricted based
on two properties.
2019-10-17 09:24:51 +00:00
2024-07-12 07:34:16 +00:00
Attributes:
raised_error: permission to be raised
2019-10-17 09:24:51 +00:00
"""
raised_error = PermissionDenied
2024-07-12 07:34:16 +00:00
@staticmethod
def permission_function(obj: Any, user: User) -> bool:
"""Function to test permission with."""
return False
2019-10-17 09:24:51 +00:00
@classmethod
def get_permission_function(cls, obj, user):
return cls.permission_function(obj, user)
def dispatch(self, request, *arg, **kwargs):
if hasattr(self, "get_object") and callable(self.get_object):
self.object = self.get_object()
if not self.get_permission_function(self.object, request.user):
raise self.raised_error
2024-06-27 12:46:43 +00:00
return super().dispatch(request, *arg, **kwargs)
2019-10-17 09:24:51 +00:00
# If we get here, it's a ListView
queryset = self.get_queryset()
l_id = [o.id for o in queryset if self.get_permission_function(o, request.user)]
if not l_id and queryset.count() != 0:
raise self.raised_error
self._get_queryset = self.get_queryset
def get_qs(self2):
return self2._get_queryset().filter(id__in=l_id)
self.get_queryset = types.MethodType(get_qs, self)
2024-06-27 12:46:43 +00:00
return super().dispatch(request, *arg, **kwargs)
2019-10-17 09:24:51 +00:00
class CanCreateMixin(View):
2024-07-12 07:34:16 +00:00
"""Protect any child view that would create an object.
2024-07-12 07:34:16 +00:00
Raises:
PermissionDenied:
If the user has not the necessary permission
to create the object of the view.
"""
2018-10-04 19:29:19 +00:00
2016-11-05 12:37:30 +00:00
def dispatch(self, request, *arg, **kwargs):
2024-06-27 12:46:43 +00:00
res = super().dispatch(request, *arg, **kwargs)
if not request.user.is_authenticated:
2016-11-05 12:37:30 +00:00
raise PermissionDenied
return res
def form_valid(self, form):
obj = form.instance
if can_edit_prop(obj, self.request.user):
2024-06-27 12:46:43 +00:00
return super().form_valid(form)
raise PermissionDenied
2018-10-04 19:29:19 +00:00
class CanEditPropMixin(GenericContentPermissionMixinBuilder):
2024-07-12 07:34:16 +00:00
"""Ensure the user has owner permissions on the child view object.
In other word, you can make a view with this view as parent,
and it will be retricted to the users that are in the
object's owner_group or that pass the `obj.can_be_viewed_by` test.
2024-07-12 07:34:16 +00:00
Raises:
PermissionDenied: If the user cannot see the object
"""
2018-10-04 19:29:19 +00:00
2019-10-17 09:24:51 +00:00
permission_function = can_edit_prop
2018-10-04 19:29:19 +00:00
class CanEditMixin(GenericContentPermissionMixinBuilder):
2024-07-12 07:34:16 +00:00
"""Ensure the user has permission to edit this view's object.
2024-07-12 07:34:16 +00:00
Raises:
PermissionDenied: if the user cannot edit this view's object.
"""
2018-10-04 19:29:19 +00:00
2019-10-17 09:24:51 +00:00
permission_function = can_edit
2018-10-04 19:29:19 +00:00
class CanViewMixin(GenericContentPermissionMixinBuilder):
2024-07-12 07:34:16 +00:00
"""Ensure the user has permission to view this view's object.
2024-07-12 07:34:16 +00:00
Raises:
PermissionDenied: if the user cannot edit this view's object.
"""
2018-10-04 19:29:19 +00:00
2019-10-17 09:24:51 +00:00
permission_function = can_view
2016-08-24 17:50:22 +00:00
2017-07-22 20:35:01 +00:00
class UserIsRootMixin(GenericContentPermissionMixinBuilder):
2024-07-12 07:34:16 +00:00
"""Allow only root admins.
2024-07-12 07:34:16 +00:00
Raises:
PermissionDenied: if the user isn't root
"""
@staticmethod
def permission_function(obj: Any, user: User):
return user.is_root
2024-07-18 18:23:30 +00:00
class FormerSubscriberMixin(AccessMixin):
2024-07-12 07:34:16 +00:00
"""Check if the user was at least an old subscriber.
2024-07-12 07:34:16 +00:00
Raises:
PermissionDenied: if the user never subscribed.
2017-07-22 20:35:01 +00:00
"""
2018-10-04 19:29:19 +00:00
2017-07-22 20:35:01 +00:00
def dispatch(self, request, *args, **kwargs):
if not request.user.was_subscribed:
raise PermissionDenied
2024-06-27 12:46:43 +00:00
return super().dispatch(request, *args, **kwargs)
2017-07-22 20:35:01 +00:00
2024-07-18 18:23:30 +00:00
class SubscriberMixin(AccessMixin):
def dispatch(self, request, *args, **kwargs):
2024-07-18 18:23:30 +00:00
if not request.user.is_subscribed:
return self.handle_no_permission()
2024-06-27 12:46:43 +00:00
return super().dispatch(request, *args, **kwargs)
class TabedViewMixin(View):
2024-07-12 07:34:16 +00:00
"""Basic functions for displaying tabs in the template."""
2018-10-04 19:29:19 +00:00
def get_tabs_title(self):
2024-06-27 12:46:43 +00:00
if hasattr(self, "tabs_title"):
return self.tabs_title
2024-06-27 12:46:43 +00:00
raise ImproperlyConfigured("tabs_title is required")
def get_current_tab(self):
2024-06-27 12:46:43 +00:00
if hasattr(self, "current_tab"):
return self.current_tab
2024-06-27 12:46:43 +00:00
raise ImproperlyConfigured("current_tab is required")
def get_list_of_tabs(self):
2024-06-27 12:46:43 +00:00
if hasattr(self, "list_of_tabs"):
return self.list_of_tabs
2024-06-27 12:46:43 +00:00
raise ImproperlyConfigured("list_of_tabs is required")
def get_context_data(self, **kwargs):
2024-06-27 12:46:43 +00:00
kwargs = super().get_context_data(**kwargs)
2018-10-04 19:29:19 +00:00
kwargs["tabs_title"] = self.get_tabs_title()
kwargs["current_tab"] = self.get_current_tab()
kwargs["list_of_tabs"] = self.get_list_of_tabs()
return kwargs
2018-10-04 19:29:19 +00:00
2017-01-10 16:45:49 +00:00
class QuickNotifMixin:
2017-01-07 14:07:28 +00:00
quick_notif_list = []
2017-01-10 16:45:49 +00:00
def dispatch(self, request, *arg, **kwargs):
# In some cases, the class can stay instanciated, so we need to reset the list
self.quick_notif_list = []
2024-06-27 12:46:43 +00:00
return super().dispatch(request, *arg, **kwargs)
2017-01-10 16:45:49 +00:00
2017-01-07 14:07:28 +00:00
def get_success_url(self):
2024-06-27 12:46:43 +00:00
ret = super().get_success_url()
if hasattr(self, "quick_notif_url_arg"):
2018-10-04 19:29:19 +00:00
if "?" in ret:
ret += "&" + self.quick_notif_url_arg
2017-01-07 14:07:28 +00:00
else:
2018-10-04 19:29:19 +00:00
ret += "?" + self.quick_notif_url_arg
2017-01-07 14:07:28 +00:00
return ret
def get_context_data(self, **kwargs):
2024-07-12 07:34:16 +00:00
"""Add quick notifications to context."""
2024-06-27 12:46:43 +00:00
kwargs = super().get_context_data(**kwargs)
2018-10-04 19:29:19 +00:00
kwargs["quick_notifs"] = []
2017-01-07 14:07:28 +00:00
for n in self.quick_notif_list:
2018-10-04 19:29:19 +00:00
kwargs["quick_notifs"].append(settings.SITH_QUICK_NOTIF[n])
for key, val in settings.SITH_QUICK_NOTIF.items():
for gk in self.request.GET:
if key == gk:
kwargs["quick_notifs"].append(val)
2017-01-07 14:07:28 +00:00
return kwargs
2019-04-10 11:21:34 +00:00
class DetailFormView(SingleObjectMixin, FormView):
2024-07-12 07:34:16 +00:00
"""Class that allow both a detail view and a form view."""
2019-04-10 11:21:34 +00:00
def get_object(self):
2024-07-12 07:34:16 +00:00
"""Get current group from id in url."""
2019-04-10 11:21:34 +00:00
return self.cached_object
@cached_property
def cached_object(self):
2024-07-12 07:34:16 +00:00
"""Optimisation on group retrieval."""
2024-06-27 12:46:43 +00:00
return super().get_object()
2019-04-10 11:21:34 +00:00
class AllowFragment:
2024-10-19 14:24:49 +00:00
"""Add `is_fragment` to templates. It's only True if the request is emitted by htmx"""
def get_context_data(self, **kwargs):
kwargs["is_fragment"] = self.request.headers.get("HX-Request", False)
return super().get_context_data(**kwargs)
# F403: those star-imports would be hellish to refactor
# E402: putting those import at the top of the file would also be difficult
from .files import * # noqa: F403 E402
from .group import * # noqa: F403 E402
from .page import * # noqa: F403 E402
from .site import * # noqa: F403 E402
from .user import * # noqa: F403 E402