8
0
Fork 0
mirror of https://gitlab2.federez.net/re2o/re2o synced 2024-11-26 06:32:26 +00:00

Documentation des décorateurs d'ACL

This commit is contained in:
Hugo LEVY-FALK 2018-05-07 18:57:08 +02:00
parent 586321fd8a
commit 84a901e3fc

View file

@ -37,24 +37,89 @@ from django.urls import reverse
def acl_base_decorator(method_name, *targets, **kwargs): def acl_base_decorator(method_name, *targets, **kwargs):
"""Base decorator for acl. It checks if the user has the permission by """Base decorator for acl. It checks if the `request.user` has the
calling model.method_name. If the flag on_instance is True, tries to get an permission by calling model.method_name. If the flag on_instance is True,
instance of the model by calling model.get_instance(*args, **kwargs) and tries to get an instance of the model by calling
runs instance.mehod_name rather than model.method_name. `model.get_instance(*args, **kwargs)` and runs `instance.mehod_name`
rather than model.method_name.
It is not intended to be used as is. It is a base for others ACL
decorators.
Args:
method_name: The name of the method which is to to be used for ACL.
(ex: 'can_edit') WARNING: if no method called 'method_name' exists,
then no error will be triggered, the decorator will act as if
permission was granted. This is to allow you to run ACL tests on
fields only. If the method exists, it has to return a 2-tuple
`(can, reason)` with `can` being a boolean stating whether the
access is granted and `reason` a message to be displayed if `can`
equals `False` (can be `None`)
*targets: The targets. Targets are specified like a sequence of models
and fields names. As an example
```
acl_base_decorator('can_edit', ModelA, 'field1', 'field2', \
ModelB, ModelC, 'field3', on_instance=False)
```
will make the following calls (where `user` is the current user,
`*args` and `**kwargs` are the arguments initially passed to the
view):
- `ModelA.can_edit(user, *args, **kwargs)`
- `ModelA.can_change_field1(user, *args, **kwargs)`
- `ModelA.can_change_field2(user, *args, **kwargs)`
- `ModelB.can_edit(user, *args, **kwargs)`
- `ModelC.can_edit(user, *args, **kwargs)`
- `ModelC.can_change_field3(user, *args, **kwargs)`
Note that
```
acl_base_decorator('can_edit', 'field1', ModelA, 'field2', \
on_instance=False)
```
would have the same effect that
```
acl_base_decorator('can_edit', ModelA, 'field1', 'field2', \
on_instance=False)
```
But don't do that, it's silly.
**kwargs: There is only one keyword argument, `on_instance`, which
default value is `True`. When `on_instance` equals `False`, the
decorator runs the ACL method on the model class rather than on
an instance. If an instance need to fetched, it is done calling the
assumed existing method `get_instance` of the model, with the
arguments originally passed to the view.
Returns:
The user is either redirected to their own page with an explanation
message if at least one access is not granted, or to the view. In order
to avoid duplicate DB calls, when the `on_instance` flag equals `True`,
the instances are passed to the view. Example, with this decorator:
```
acl_base_decorator('can_edit', ModelA, 'field1', 'field2', ModelB,\
ModelC)
```
The view will be called like this:
```
view(request, instance_of_A, instance_of_b, *args, **kwargs)
```
where `*args` and `**kwargs` are the original view arguments.
""" """
on_instance = kwargs.get('on_instance', True) on_instance = kwargs.get('on_instance', True)
def group_targets(): def group_targets():
"""This generator parses the targets of the decorator, yielding
2-tuples of (model, [fields]).
"""
current_target = None current_target = None
current_fields = [] current_fields = []
for t in targets: for target in targets:
if isinstance(t, type) and issubclass(t, Model): if isinstance(target, type) and issubclass(target, Model):
if current_target: if current_target:
yield (current_target, current_fields) yield (current_target, current_fields)
current_target = t current_target = target
current_fields = [] current_fields = []
else: else:
current_fields.append(t) current_fields.append(target)
yield (current_target, current_fields) yield (current_target, current_fields)
def decorator(view): def decorator(view):
@ -65,6 +130,11 @@ def acl_base_decorator(method_name, *targets, **kwargs):
instances = [] instances = []
def process_target(target, fields): def process_target(target, fields):
"""This function calls the methods on the target and checks for
the can_change_`field` method with the given fields. It also
stores the instances of models in order to avoid duplicate DB
calls for the view.
"""
if on_instance: if on_instance:
try: try:
target = target.get_instance(*args, **kwargs) target = target.get_instance(*args, **kwargs)
@ -97,37 +167,37 @@ def acl_base_decorator(method_name, *targets, **kwargs):
def can_create(*models): def can_create(*models):
"""Decorator to check if an user can create a model. """Decorator to check if an user can create the given models. It runs
It assumes that a valid user exists in the request and that the model has a `acl_base_decorator` with the flag `on_instance=False` and the method
method can_create(user) which returns true if the user can create this kind 'can_create'. See `acl_base_decorator` documentation for further details.
of models.
""" """
return acl_base_decorator('can_create', *models, on_instance=False) return acl_base_decorator('can_create', *models, on_instance=False)
def can_edit(*targets): def can_edit(*targets):
"""Decorator to check if an user can edit a model. """Decorator to check if an user can edit the models.
It tries to get an instance of the model, using It runs `acl_base_decorator` with the flag `on_instance=True` and the
`model.get_instance(*args, **kwargs)` and assumes that the model has a method 'can_edit'. See `acl_base_decorator` documentation for further
method `can_edit(user)` which returns `true` if the user can edit this details.
kind of models.
""" """
return acl_base_decorator('can_edit', *targets) return acl_base_decorator('can_edit', *targets)
def can_change(*targets): def can_change(*targets):
"""Decorator to check if an user can edit a field of a model class. """Decorator to check if an user can edit a field of a model class.
Difference with can_edit : take a class and not an instance Difference with can_edit : takes a class and not an instance
It runs `acl_base_decorator` with the flag `on_instance=False` and the
method 'can_change'. See `acl_base_decorator` documentation for further
details.
""" """
return acl_base_decorator('can_change', *targets) return acl_base_decorator('can_change', *targets)
def can_delete(*targets): def can_delete(*targets):
"""Decorator to check if an user can delete a model. """Decorator to check if an user can delete a model.
It tries to get an instance of the model, using It runs `acl_base_decorator` with the flag `on_instance=True` and the
`model.get_instance(*args, **kwargs)` and assumes that the model has a method 'can_edit'. See `acl_base_decorator` documentation for further
method `can_delete(user)` which returns `true` if the user can delete this details.
kind of models.
""" """
return acl_base_decorator('can_delete', *targets) return acl_base_decorator('can_delete', *targets)
@ -162,16 +232,18 @@ def can_delete_set(model):
def can_view(*targets): def can_view(*targets):
"""Decorator to check if an user can view a model. """Decorator to check if an user can view a model.
It tries to get an instance of the model, using It runs `acl_base_decorator` with the flag `on_instance=True` and the
`model.get_instance(*args, **kwargs)` and assumes that the model has a method 'can_view'. See `acl_base_decorator` documentation for further
method `can_view(user)` which returns `true` if the user can view this details.
kind of models.
""" """
return acl_base_decorator('can_view', *targets) return acl_base_decorator('can_view', *targets)
def can_view_all(*targets): def can_view_all(*targets):
"""Decorator to check if an user can view a class of model. """Decorator to check if an user can view a class of model.
It runs `acl_base_decorator` with the flag `on_instance=False` and the
method 'can_view_all'. See `acl_base_decorator` documentation for further
details.
""" """
return acl_base_decorator('can_view_all', *targets, on_instance=False) return acl_base_decorator('can_view_all', *targets, on_instance=False)