8
0
Fork 0
mirror of https://gitlab2.federez.net/re2o/re2o synced 2024-11-23 20:03:11 +00:00

Fix can_view_all so it actually does its job.

This commit is contained in:
Hugo Levy-Falk 2020-08-28 19:41:36 +01:00 committed by Gabriel Detraz
parent d5831c5f1f
commit 360458541a
2 changed files with 62 additions and 2 deletions

View file

@ -57,6 +57,9 @@ def acl_error_message(msg, permissions):
) )
# This is the function of main interest of this file. Almost all the decorators
# use it, and it is a fairly complicated piece of code. Let me guide you through
# this ! 🌈😸
def acl_base_decorator(method_name, *targets, on_instance=True): def acl_base_decorator(method_name, *targets, on_instance=True):
"""Base decorator for acl. It checks if the `request.user` has the """Base decorator for acl. It checks if the `request.user` has the
permission by calling model.method_name. If the flag on_instance is True, permission by calling model.method_name. If the flag on_instance is True,
@ -128,22 +131,43 @@ ModelC)
where `*args` and `**kwargs` are the original view arguments. where `*args` and `**kwargs` are the original view arguments.
""" """
# First we define a utilitary functions. This is what parses the input of
#  the decorator. It will group a target (i.e. a model class) with a list
# of associated fields (possibly empty).
def group_targets(): def group_targets():
"""This generator parses the targets of the decorator, yielding """This generator parses the targets of the decorator, yielding
2-tuples of (model, [fields]). 2-tuples of (model, [fields]).
""" """
current_target = None current_target = None
current_fields = [] current_fields = []
# We iterate over all the possible target passed in argument of the
# decorator. Let's call the `target` variable a target candidate.
# We basically want to discriminate the valid targets over the field
# names.
for target in targets: for target in targets:
# We enter this conditional block if the current target is not
# a string, i.e. if it is not a field name, i.e. it is a model
# name.
if not isinstance(target, str): if not isinstance(target, str):
# if the current target is defined, this means we already
# encountered a valid target and we have been storing field
# names ever since. This group is ready and we can `yield` it.
if current_target: if current_target:
yield (current_target, current_fields) yield (current_target, current_fields)
# Then we define the current target and reset its fields.
current_target = target current_target = target
current_fields = [] current_fields = []
else: else:
# When we encounter a string, this is not valid target and is
# thus a field name. We store it for later.
current_fields.append(target) current_fields.append(target)
# We need to yield the last pair of target and fields.
yield (current_target, current_fields) yield (current_target, current_fields)
# Now to the main topic ! if you are not sure why we need to use a function
# `wrapper` inside the `decorator` function, you need to read some
#  documentation on decorators !
def decorator(view): def decorator(view):
"""The decorator to use on a specific view """The decorator to use on a specific view
""" """
@ -158,43 +182,74 @@ ModelC)
stores the instances of models in order to avoid duplicate DB stores the instances of models in order to avoid duplicate DB
calls for the view. calls for the view.
""" """
# When working on instances, retrieve the associated instance
# and store it to pass it to the view.
if on_instance: if on_instance:
try: try:
target = target.get_instance(target_id, *args, **kwargs) target = target.get_instance(target_id, *args, **kwargs)
instances.append(target) instances.append(target)
except target.DoesNotExist: except target.DoesNotExist:
# A non existing instance is a valid reason to deny
# access to the view.
yield False, _("Nonexistent entry."), [] yield False, _("Nonexistent entry."), []
return return
# Now we can actually make the ACL test, using the right ACL
# method.
if hasattr(target, method_name): if hasattr(target, method_name):
can_fct = getattr(target, method_name) can_fct = getattr(target, method_name)
yield can_fct(request.user, *args, **kwargs) yield can_fct(request.user, *args, **kwargs)
# If working on fields, iterate through the concerned ones
# and check that the user can change this field. (this is
# the only available ACL for fields)
for field in fields: for field in fields:
can_change_fct = getattr(target, "can_change_" + field) can_change_fct = getattr(target, "can_change_" + field)
yield can_change_fct(request.user, *args, **kwargs) yield can_change_fct(request.user, *args, **kwargs)
# Now to the main loop. We are going iterate through the targets
# pairs (remember the `group_targets` function) and the keyword
# arguments of the view to retrieve the associated model instances
# and check that the user making the request is authorized to do it
# as well as storing the the associated error and warning messages.
error_messages = [] error_messages = []
warning_messages = [] warning_messages = []
for arg_key, (target, fields) in zip(kwargs.keys(), group_targets()):
if on_instance:
iterator = zip(kwargs.keys(), group_targets())
else:
iterator = group_targets()
for it in iterator:
# If the decorator must work on instances, retrieve the
# associated instance.
if on_instance: if on_instance:
arg_key, (target, fields) = it
target_id = int(kwargs[arg_key]) target_id = int(kwargs[arg_key])
else: else:
target, fields = it
target_id = None target_id = None
# Store the messages at the right place.
for can, msg, permissions in process_target(target, fields, target_id): for can, msg, permissions in process_target(target, fields, target_id):
if not can: if not can:
error_messages.append(acl_error_message(msg, permissions)) error_messages.append(acl_error_message(msg, permissions))
elif msg: elif msg:
warning_messages.append(acl_error_message(msg, permissions)) warning_messages.append(acl_error_message(msg, permissions))
# Display the warning messages
if warning_messages: if warning_messages:
for msg in warning_messages: for msg in warning_messages:
messages.warning(request, msg) messages.warning(request, msg)
# If there is any error message, then the request must be denied.
if error_messages: if error_messages:
# We display the message
for msg in error_messages: for msg in error_messages:
messages.error( messages.error(
request, request,
msg or _("You don't have the right to access this menu."), msg or _("You don't have the right to access this menu."),
) )
# And redirect the user to the right place.
if request.user.id is not None: if request.user.id is not None:
return redirect( return redirect(
reverse("users:profil", kwargs={"userid": str(request.user.id)}) reverse("users:profil", kwargs={"userid": str(request.user.id)})

View file

@ -31,6 +31,7 @@ from django import template
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.forms import TextInput from django.forms import TextInput
from django.forms.widgets import Select from django.forms.widgets import Select
from django.utils.translation import ugettext_lazy as _
from bootstrap3.utils import render_tag from bootstrap3.utils import render_tag
from bootstrap3.forms import render_field from bootstrap3.forms import render_field
@ -244,6 +245,7 @@ class MBFForm:
self.html += mbf_field.render() self.html += mbf_field.render()
else: else:
f = field.get_bound_field(self.form, name), self.args, self.kwargs
self.html += render_field( self.html += render_field(
field.get_bound_field(self.form, name), field.get_bound_field(self.form, name),
*self.args, *self.args,
@ -406,7 +408,10 @@ class MBFField:
self.html += render_field(self.bound, *self.args, **self.kwargs) self.html += render_field(self.bound, *self.args, **self.kwargs)
self.field.widget = TextInput( self.field.widget = TextInput(
attrs={"name": "mbf_" + self.name, "placeholder": self.field.empty_label} attrs={
"name": "mbf_" + self.name,
"placeholder": getattr(self.field, "empty_label", _("Nothing")),
}
) )
self.replace_input = render_field(self.bound, *self.args, **self.kwargs) self.replace_input = render_field(self.bound, *self.args, **self.kwargs)