Sith/core/views/__init__.py

370 lines
10 KiB
Python
Raw Permalink Normal View History

# -*- coding:utf-8 -*
#
# 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
from sentry_sdk import last_event_id
from django.shortcuts import render
from django.http import (
HttpResponseForbidden,
HttpResponseNotFound,
HttpResponseServerError,
)
from django.template import RequestContext
2018-10-04 19:29:19 +00:00
from django.core.exceptions import (
PermissionDenied,
ObjectDoesNotExist,
ImproperlyConfigured,
)
from django.views.generic.base import View
2019-04-10 11:21:34 +00:00
from django.views.generic.edit import FormView
from django.views.generic.detail import SingleObjectMixin
from django.utils.functional import cached_property
2017-02-05 14:22:52 +00:00
from django.db.models import Count
from core.models import Group
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):
try:
2018-10-04 19:29:19 +00:00
return HttpResponseForbidden(
render(
request,
"core/403.jinja",
context={
"next": request.path,
"form": LoginForm(),
"popup": request.resolver_match.kwargs["popup"] or "",
},
)
)
except:
2018-10-04 19:29:19 +00:00
return HttpResponseForbidden(
render(
request,
"core/403.jinja",
context={"next": request.path, "form": LoginForm()},
)
)
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"))
2016-02-05 15:59:42 +00:00
def can_edit_prop(obj, user):
"""
:param obj: Object to test for permission
:param user: core.models.User to test permissions against
:return: if user is authorized to edit object properties
:rtype: bool
:Example:
.. code-block:: python
if not can_edit_prop(self.object ,request.user):
raise PermissionDenied
"""
2016-02-05 15:59:42 +00:00
if obj is None or user.is_owner(obj):
return True
return False
2018-10-04 19:29:19 +00:00
2016-02-05 15:59:42 +00:00
def can_edit(obj, user):
"""
:param obj: Object to test for permission
:param user: core.models.User to test permissions against
:return: if user is authorized to edit object
:rtype: bool
:Example:
.. code-block:: python
if not can_edit(self.object ,request.user):
raise PermissionDenied
"""
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
2016-02-05 15:59:42 +00:00
def can_view(obj, user):
"""
:param obj: Object to test for permission
:param user: core.models.User to test permissions against
:return: if user is authorized to see object
:rtype: bool
:Example:
.. code-block:: python
if not can_view(self.object ,request.user):
raise PermissionDenied
"""
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):
2019-10-17 09:24:51 +00:00
"""
Used to build permission mixins
This view protect any child view that would be showing an object that is restricted based
on two properties
:prop permission_function: function to test permission with, takes an object and an user an return a bool
:prop raised_error: permission to be raised
:raises: raised_error
"""
permission_function = lambda obj, user: False
raised_error = PermissionDenied
@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
return super(GenericContentPermissionMixinBuilder, self).dispatch(
2019-10-17 09:24:51 +00:00
request, *arg, **kwargs
)
# 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)
return super(GenericContentPermissionMixinBuilder, self).dispatch(
request, *arg, **kwargs
)
2019-10-17 09:24:51 +00:00
class CanCreateMixin(View):
"""
This view is made to protect any child view that would create an object, and thus, that can not be protected by any
of the following mixin
:raises: PermissionDenied
"""
2018-10-04 19:29:19 +00:00
2016-11-05 12:37:30 +00:00
def dispatch(self, request, *arg, **kwargs):
res = super(CanCreateMixin, self).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):
return super(CanCreateMixin, self).form_valid(form)
raise PermissionDenied
2018-10-04 19:29:19 +00:00
class CanEditPropMixin(GenericContentPermissionMixinBuilder):
"""
This view is made to protect any child view that would be showing some properties of an object that are restricted
to only the owner group of the given object.
In other word, you can make a view with this view as parent, and it would be retricted to the users that are in the
object's owner_group
:raises: PermissionDenied
"""
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):
"""
This view makes exactly the same thing as its direct parent, but checks the group on the edit_groups field of the
object
:raises: PermissionDenied
"""
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):
"""
This view still makes exactly the same thing as its direct parent, but checks the group on the view_groups field of
the object
:raises: PermissionDenied
"""
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):
"""
This view check if the user is root
:raises: PermissionDenied
"""
permission_function = lambda obj, user: user.is_root
2017-07-24 10:33:34 +00:00
class FormerSubscriberMixin(View):
2017-07-22 20:35:01 +00:00
"""
This view check if the user was at least an old subscriber
:raises: PermissionDenied
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
2017-07-24 10:33:34 +00:00
return super(FormerSubscriberMixin, self).dispatch(request, *args, **kwargs)
2017-07-22 20:35:01 +00:00
class UserIsLoggedMixin(View):
"""
This view check if the user is logged
:raises: PermissionDenied
"""
def dispatch(self, request, *args, **kwargs):
if request.user.is_anonymous:
raise PermissionDenied
return super(UserIsLoggedMixin, self).dispatch(request, *args, **kwargs)
class TabedViewMixin(View):
"""
This view provide the basic functions for displaying tabs in the template
"""
2018-10-04 19:29:19 +00:00
def get_tabs_title(self):
try:
return self.tabs_title
except:
raise ImproperlyConfigured("tabs_title is required")
def get_current_tab(self):
try:
return self.current_tab
except:
raise ImproperlyConfigured("current_tab is required")
def get_list_of_tabs(self):
try:
return self.list_of_tabs
except:
raise ImproperlyConfigured("list_of_tabs is required")
def get_context_data(self, **kwargs):
kwargs = super(TabedViewMixin, self).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):
2018-10-04 19:29:19 +00:00
self.quick_notif_list = (
[]
) # In some cases, the class can stay instanciated, so we need to reset the list
2017-01-10 16:45:49 +00:00
return super(QuickNotifMixin, self).dispatch(request, *arg, **kwargs)
2017-01-07 14:07:28 +00:00
def get_success_url(self):
ret = super(QuickNotifMixin, self).get_success_url()
try:
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
except:
pass
2017-01-07 14:07:28 +00:00
return ret
def get_context_data(self, **kwargs):
"""Add quick notifications to context"""
kwargs = super(QuickNotifMixin, self).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 k, v in settings.SITH_QUICK_NOTIF.items():
2017-01-07 14:07:28 +00:00
for gk in self.request.GET.keys():
if k == gk:
2018-10-04 19:29:19 +00:00
kwargs["quick_notifs"].append(v)
2017-01-07 14:07:28 +00:00
return kwargs
2019-04-10 11:21:34 +00:00
class DetailFormView(SingleObjectMixin, FormView):
"""
2020-08-27 13:59:42 +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):
"""
2020-08-27 13:59:42 +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):
"""
2020-08-27 13:59:42 +00:00
Optimisation on group retrieval
2019-04-10 11:21:34 +00:00
"""
return super(DetailFormView, self).get_object()
2015-11-24 15:09:46 +00:00
from .user import *
from .page import *
2016-08-10 03:48:06 +00:00
from .files import *
2015-11-24 15:09:46 +00:00
from .site import *
from .group import *