|
|
|
@ -37,24 +37,89 @@ from django.urls import reverse |
|
|
|
|
|
|
|
|
|
|
|
def acl_base_decorator(method_name, *targets, **kwargs): |
|
|
|
"""Base decorator for acl. It checks if the user has the permission by |
|
|
|
calling model.method_name. If the flag on_instance is True, tries to get an |
|
|
|
instance of the model by calling model.get_instance(*args, **kwargs) and |
|
|
|
runs instance.mehod_name rather than model.method_name. |
|
|
|
"""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, |
|
|
|
tries to get an instance of the model by calling |
|
|
|
`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) |
|
|
|
|
|
|
|
def group_targets(): |
|
|
|
"""This generator parses the targets of the decorator, yielding |
|
|
|
2-tuples of (model, [fields]). |
|
|
|
""" |
|
|
|
current_target = None |
|
|
|
current_fields = [] |
|
|
|
for t in targets: |
|
|
|
if isinstance(t, type) and issubclass(t, Model): |
|
|
|
for target in targets: |
|
|
|
if isinstance(target, type) and issubclass(target, Model): |
|
|
|
if current_target: |
|
|
|
yield (current_target, current_fields) |
|
|
|
current_target = t |
|
|
|
current_target = target |
|
|
|
current_fields = [] |
|
|
|
else: |
|
|
|
current_fields.append(t) |
|
|
|
current_fields.append(target) |
|
|
|
yield (current_target, current_fields) |
|
|
|
|
|
|
|
def decorator(view): |
|
|
|
@ -65,6 +130,11 @@ def acl_base_decorator(method_name, *targets, **kwargs): |
|
|
|
instances = [] |
|
|
|
|
|
|
|
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: |
|
|
|
try: |
|
|
|
target = target.get_instance(*args, **kwargs) |
|
|
|
@ -97,37 +167,37 @@ def acl_base_decorator(method_name, *targets, **kwargs): |
|
|
|
|
|
|
|
|
|
|
|
def can_create(*models): |
|
|
|
"""Decorator to check if an user can create a model. |
|
|
|
It assumes that a valid user exists in the request and that the model has a |
|
|
|
method can_create(user) which returns true if the user can create this kind |
|
|
|
of models. |
|
|
|
"""Decorator to check if an user can create the given models. It runs |
|
|
|
`acl_base_decorator` with the flag `on_instance=False` and the method |
|
|
|
'can_create'. See `acl_base_decorator` documentation for further details. |
|
|
|
""" |
|
|
|
return acl_base_decorator('can_create', *models, on_instance=False) |
|
|
|
|
|
|
|
|
|
|
|
def can_edit(*targets): |
|
|
|
"""Decorator to check if an user can edit a model. |
|
|
|
It tries to get an instance of the model, using |
|
|
|
`model.get_instance(*args, **kwargs)` and assumes that the model has a |
|
|
|
method `can_edit(user)` which returns `true` if the user can edit this |
|
|
|
kind of models. |
|
|
|
"""Decorator to check if an user can edit the models. |
|
|
|
It runs `acl_base_decorator` with the flag `on_instance=True` and the |
|
|
|
method 'can_edit'. See `acl_base_decorator` documentation for further |
|
|
|
details. |
|
|
|
""" |
|
|
|
return acl_base_decorator('can_edit', *targets) |
|
|
|
|
|
|
|
|
|
|
|
def can_change(*targets): |
|
|
|
"""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) |
|
|
|
|
|
|
|
|
|
|
|
def can_delete(*targets): |
|
|
|
"""Decorator to check if an user can delete a model. |
|
|
|
It tries to get an instance of the model, using |
|
|
|
`model.get_instance(*args, **kwargs)` and assumes that the model has a |
|
|
|
method `can_delete(user)` which returns `true` if the user can delete this |
|
|
|
kind of models. |
|
|
|
It runs `acl_base_decorator` with the flag `on_instance=True` and the |
|
|
|
method 'can_edit'. See `acl_base_decorator` documentation for further |
|
|
|
details. |
|
|
|
""" |
|
|
|
return acl_base_decorator('can_delete', *targets) |
|
|
|
|
|
|
|
@ -162,16 +232,18 @@ def can_delete_set(model): |
|
|
|
|
|
|
|
def can_view(*targets): |
|
|
|
"""Decorator to check if an user can view a model. |
|
|
|
It tries to get an instance of the model, using |
|
|
|
`model.get_instance(*args, **kwargs)` and assumes that the model has a |
|
|
|
method `can_view(user)` which returns `true` if the user can view this |
|
|
|
kind of models. |
|
|
|
It runs `acl_base_decorator` with the flag `on_instance=True` and the |
|
|
|
method 'can_view'. See `acl_base_decorator` documentation for further |
|
|
|
details. |
|
|
|
""" |
|
|
|
return acl_base_decorator('can_view', *targets) |
|
|
|
|
|
|
|
|
|
|
|
def can_view_all(*targets): |
|
|
|
"""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) |
|
|
|
|
|
|
|
|