field_permissions.py 3.98 KB
Newer Older
Maël Kervella's avatar
Maël Kervella committed
1 2 3 4 5 6
# -*- mode: python; coding: utf-8 -*-
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2017  Gabriel Détraz
Hugo Levy-Falk's avatar
Hugo Levy-Falk committed
7
# Copyright © 2017  Lara Kermarec
Maël Kervella's avatar
Maël Kervella committed
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
# Copyright © 2017  Augustin Lemesle
# Copyright © 2018  Maël Kervella
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""re2o.field_permissions
A model mixin and a field mixin used to remove some unauthorized fields
from the form automatically generated from the model. The model must
subclass `FieldPermissionModelMixin` and the form must subclass
`FieldPermissionFieldMixin` so when a Django form is generated from the
fields of the models, some fields will be removed if the user don't have
the rights to change them (can_change_{name})
"""
32

Hugo Levy-Falk's avatar
Hugo Levy-Falk committed
33

34
class FieldPermissionModelMixin:
Maël Kervella's avatar
Maël Kervella committed
35
    """ The model mixin. Defines the `has_field_perm` function """
Hugo Levy-Falk's avatar
Hugo Levy-Falk committed
36

37
    field_permissions = {}  # {'field_name': callable}
Hugo Levy-Falk's avatar
Hugo Levy-Falk committed
38 39
    FIELD_PERM_CODENAME = "can_change_{model}_{name}"
    FIELD_PERMISSION_GETTER = "can_change_{name}"
40 41 42
    FIELD_PERMISSION_MISSING_DEFAULT = True

    def has_field_perm(self, user, field):
Maël Kervella's avatar
Maël Kervella committed
43 44
        """ Checks if a `user` has the right to edit the `field`
        of this model """
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
        if field in self.field_permissions:
            checks = self.field_permissions[field]
            if not isinstance(checks, (list, tuple)):
                checks = [checks]

        else:
            checks = []

            # Consult the optional field-specific hook.
            getter_name = self.FIELD_PERMISSION_GETTER.format(name=field)
            if hasattr(self, getter_name):
                checks.append(getattr(self, getter_name))

            # Try to find a static permission for the field
            else:
Hugo Levy-Falk's avatar
Hugo Levy-Falk committed
60 61 62
                perm_label = self.FIELD_PERM_CODENAME.format(
                    **{"model": self._meta.model_name, "name": field}
                )
63 64 65 66 67 68 69 70 71 72
                if perm_label in dict(self._meta.permissions):
                    checks.append(perm_label)

        # No requirements means no restrictions.
        if not len(checks):
            return self.FIELD_PERMISSION_MISSING_DEFAULT

        # Try to find a user setting that qualifies them for permission.
        for perm in checks:
            if callable(perm):
73
                result, _reason, _permissions = perm(user_request=user)
74 75 76
                if result is not None:
                    return result
            else:
Maël Kervella's avatar
Maël Kervella committed
77 78
                # Don't supply 'obj', or else infinite recursion.
                result = user.has_perm(perm)
79 80 81 82 83 84
                if result:
                    return True

        # If no requirement can be met, then permission is denied.
        return False

Maël Kervella's avatar
Maël Kervella committed
85

86 87
class FieldPermissionFormMixin:
    """
88
    Construit le formulaire et retire les champs interdits
89
    """
Hugo Levy-Falk's avatar
Hugo Levy-Falk committed
90

91
    def __init__(self, *args, **kwargs):
Hugo Levy-Falk's avatar
Hugo Levy-Falk committed
92
        user = kwargs.pop("user")
93 94

        super(FieldPermissionFormMixin, self).__init__(*args, **kwargs)
95
        to_be_deleted = []
96 97
        for name in self.fields:
            if not self.instance.has_field_perm(user, field=name):
98 99 100
                to_be_deleted.append(name)
        for name in to_be_deleted:
            self.remove_unauthorized_field(name)
101 102

    def remove_unauthorized_field(self, name):
Maël Kervella's avatar
Maël Kervella committed
103
        """ Remove one field from the fields of the form """
104
        del self.fields[name]