diff --git a/backend/geonature/core/admin/utils.py b/backend/geonature/core/admin/utils.py index a0b9fae2f3..6bc577d4e6 100644 --- a/backend/geonature/core/admin/utils.py +++ b/backend/geonature/core/admin/utils.py @@ -1,3 +1,5 @@ +from functools import partial + from flask import g from werkzeug.exceptions import Unauthorized @@ -33,3 +35,21 @@ def can_delete(self): @property def can_export(self): return self._can_action("E") + + +# https://github.com/flask-admin/flask-admin/issues/1807 +# https://stackoverflow.com/questions/54638047/correct-way-to-register-flask-admin-views-with-application-factory +class ReloadingIterator: + def __init__(self, iterator_factory): + self.iterator_factory = iterator_factory + + def __iter__(self): + return self.iterator_factory() + + +class DynamicOptionsMixin: + def get_dynamic_options(self, view): + raise NotImplementedError + + def get_options(self, view): + return ReloadingIterator(partial(self.get_dynamic_options, view)) diff --git a/backend/geonature/core/gn_permissions/admin.py b/backend/geonature/core/gn_permissions/admin.py index caaa2dab2a..d190f18ee4 100644 --- a/backend/geonature/core/gn_permissions/admin.py +++ b/backend/geonature/core/gn_permissions/admin.py @@ -1,9 +1,20 @@ +from flask import has_app_context, Markup from flask_admin.contrib.sqla import ModelView +from flask_admin.contrib.sqla.filters import FilterEqual from geonature.utils.env import db from geonature.core.admin.admin import admin -from geonature.core.admin.utils import CruvedProtectedMixin -from geonature.core.gn_permissions.models import PermObject, Permission, PermissionAvailable +from geonature.core.admin.utils import CruvedProtectedMixin, DynamicOptionsMixin +from geonature.core.gn_permissions.models import ( + PermObject, + PermAction, + PermScope, + Permission, + PermissionAvailable, +) +from geonature.core.gn_commons.models.base import TModules + +from pypnusershub.db.models import User class ObjectAdmin(CruvedProtectedMixin, ModelView): @@ -21,20 +32,88 @@ class ObjectAdmin(CruvedProtectedMixin, ModelView): can_delete = False +class RoleFilter(DynamicOptionsMixin, FilterEqual): + def get_dynamic_options(self, view): + if has_app_context(): + yield from [(u.id_role, u.nom_complet) for u in User.query.all()] + + +class ModuleFilter(DynamicOptionsMixin, FilterEqual): + def get_dynamic_options(self, view): + if has_app_context(): + yield from [(m.id_module, m.module_code) for m in TModules.query.all()] + + +class ObjectFilter(DynamicOptionsMixin, FilterEqual): + def get_dynamic_options(self, view): + if has_app_context(): + yield from [(o.id_object, o.code_object) for o in PermObject.query.all()] + + +class ActionFilter(DynamicOptionsMixin, FilterEqual): + def get_dynamic_options(self, view): + if has_app_context(): + yield from [(a.id_action, a.code_action) for a in PermAction.query.all()] + + +class ScopeFilter(DynamicOptionsMixin, FilterEqual): + def apply(self, query, value, alias=None): + column = self.get_column(alias) + if value: + return query.filter(column == value) + else: + return query.filter(column.is_(None)) + + def get_dynamic_options(self, view): + if has_app_context(): + yield (None, "Sans restriction") + yield from [(a.value, a.label) for a in PermScope.query.all()] + + class PermissionAdmin(CruvedProtectedMixin, ModelView): module_code = "ADMIN" object_code = "PERMISSIONS" - column_list = ("role", "module", "object", "action", "scope") + column_list = ("role", "module", "object", "action", "label", "filters") column_labels = { "role": "Rôle", - "scope": "Porté", + "filters": "Restrictions", "object": "Objet", + "role.identifiant": "identifiant du rôle", + "role.nom_complet": "nom du rôle", + "availability": "Permission disponible", } + column_searchable_list = ("role.identifiant", "role.nom_complet") column_formatters = { + "role": lambda v, c, m, p: Markup("{}".format(Markup.escape(m.role.nom_role))) + if m.role.groupe + else m.role.nom_complet, "module": lambda v, c, m, p: m.module.module_code, "object": lambda v, c, m, p: m.object.code_object, + "label": lambda v, c, m, p: m.availability.label if m.availability else None, + "filters": lambda v, c, m, p: m.scope.label if m.scope else None, } + column_filters = ( + RoleFilter(column=Permission.id_role, name="Rôle"), + ModuleFilter(column=Permission.id_module, name="Module"), + ObjectFilter(column=Permission.id_object, name="Objet"), + ActionFilter(column=Permission.id_action, name="Action"), + ScopeFilter(column=Permission.scope_value, name="Scope"), + ) + named_filter_urls = True + column_sortable_list = ( + ("role", "role.nom_complet"), + ("module", "module.module_code"), + ("object", "object.code_object"), + ("action", "action.code_action"), + ) + column_default_sort = [ + ("role.nom_complet", True), + ("module.module_code", True), + ("object.code_object", True), + ("action.code_action", True), + ] + form_columns = ("role", "module", "object", "action", "scope") class PermissionAvailableAdmin(CruvedProtectedMixin, ModelView):