"""
Models of gn_permissions schema
"""
from packaging import version
import sqlalchemy as sa
from sqlalchemy import ForeignKey, ForeignKeyConstraint
from sqlalchemy.sql import select
from sqlalchemy.orm import foreign, joinedload, contains_eager
import flask_sqlalchemy
from utils_flask_sqla.models import qfilter
from utils_flask_sqla.serializers import serializable
from pypnusershub.db.models import User
from geonature.utils.env import db
from geonature.core.gn_commons.models.base import TModules
@serializable
[docs]
class PermFilterType(db.Model):
[docs]
__tablename__ = "bib_filters_type"
[docs]
__table_args__ = {"schema": "gn_permissions"}
[docs]
id_filter_type = db.Column(db.Integer, primary_key=True)
[docs]
code_filter_type = db.Column(db.Unicode)
[docs]
label_filter_type = db.Column(db.Unicode)
[docs]
description_filter_type = db.Column(db.Unicode)
@serializable
[docs]
class PermScope(db.Model):
[docs]
__tablename__ = "bib_filters_scope"
[docs]
__table_args__ = {"schema": "gn_permissions"}
[docs]
value = db.Column(db.Integer, primary_key=True)
[docs]
label = db.Column(db.Unicode)
[docs]
description = db.Column(db.Unicode)
[docs]
def __str__(self):
return self.description
@serializable
[docs]
class PermAction(db.Model):
[docs]
__tablename__ = "bib_actions"
[docs]
__table_args__ = {"schema": "gn_permissions"}
[docs]
id_action = db.Column(db.Integer, primary_key=True)
[docs]
code_action = db.Column(db.Unicode)
[docs]
description_action = db.Column(db.Unicode)
[docs]
def __str__(self):
return self.description_action
[docs]
cor_object_module = db.Table(
"cor_object_module",
db.Column(
"id_cor_object_module",
db.Integer,
primary_key=True,
),
db.Column(
"id_object",
db.Integer,
ForeignKey("gn_permissions.t_objects.id_object"),
),
db.Column(
"id_module",
db.Integer,
ForeignKey("gn_commons.t_modules.id_module"),
),
schema="gn_permissions",
)
@serializable
[docs]
class PermObject(db.Model):
[docs]
__tablename__ = "t_objects"
[docs]
__table_args__ = {"schema": "gn_permissions"}
[docs]
id_object = db.Column(db.Integer, primary_key=True)
[docs]
code_object = db.Column(db.Unicode)
[docs]
description_object = db.Column(db.Unicode)
[docs]
def __str__(self):
return f"{self.code_object} ({self.description_object})"
# compat.
[docs]
def _nice_order(model, qs):
from geonature.core.gn_commons.models import TModules
return (
qs.join(model.module)
.join(model.object)
.join(model.action)
.options(
contains_eager(model.module),
contains_eager(model.object),
contains_eager(model.action),
)
.order_by(
TModules.module_code,
# ensure ALL at first:
sa.case([(PermObject.code_object == "ALL", "1")], else_=PermObject.code_object),
model.id_action,
)
)
[docs]
class PermissionAvailable(db.Model):
[docs]
__tablename__ = "t_permissions_available"
[docs]
__table_args__ = {"schema": "gn_permissions"}
[docs]
id_module = db.Column(
db.Integer, ForeignKey("gn_commons.t_modules.id_module"), primary_key=True
)
[docs]
id_object = db.Column(
db.Integer,
ForeignKey(PermObject.id_object),
default=select(PermObject.id_object).where(PermObject.code_object == "ALL"),
primary_key=True,
)
[docs]
id_action = db.Column(db.Integer, ForeignKey(PermAction.id_action), primary_key=True)
[docs]
label = db.Column(db.Unicode)
[docs]
module = db.relationship("TModules")
[docs]
object = db.relationship(PermObject)
[docs]
action = db.relationship(PermAction)
[docs]
scope_filter = db.Column(db.Boolean, server_default=sa.false())
[docs]
sensitivity_filter = db.Column(db.Boolean, server_default=sa.false(), nullable=False)
[docs]
filters_fields = {
"SCOPE": scope_filter,
"SENSITIVITY": sensitivity_filter,
}
@property
[docs]
def filters(self):
return [k for k, v in self.filters_fields.items() if getattr(self, v.name)]
[docs]
def __str__(self):
s = self.module.module_label
if self.object.code_object != "ALL":
object_label = self.object.code_object.title().replace("_", " ")
s += f" | {object_label}"
s += f" | {self.label}"
return s
@staticmethod
[docs]
def nice_order(**kwargs):
# TODO fix when flask admin is compatible with
# sqlalchemy2.0 query style
query = PermissionAvailable.query
return _nice_order(PermissionAvailable, query)
[docs]
class PermFilter:
def __init__(self, name, value):
self.name = name
self.value = value
[docs]
def __str__(self):
if self.name == "SCOPE":
if self.value is None:
return """<i class="fa fa-users" aria-hidden="true"></i> de tout le monde"""
elif self.value == 1:
return """<i class="fa fa-user" aria-hidden="true"></i> à moi"""
elif self.value == 2:
return """<i class="fa fa-user-circle" aria-hidden="true"></i> de mon organisme"""
elif self.name == "SENSITIVITY":
if self.value:
return """<i class="fa fa-low-vision" aria-hidden="true"></i> non sensible"""
else:
return """<i class="fa fa-eye" aria-hidden="true"></i> sensible et non sensible"""
@serializable
[docs]
class Permission(db.Model):
[docs]
__tablename__ = "t_permissions"
[docs]
__table_args__ = (
ForeignKeyConstraint(
["id_module", "id_object", "id_action"],
[
"gn_permissions.t_permissions_available.id_module",
"gn_permissions.t_permissions_available.id_object",
"gn_permissions.t_permissions_available.id_action",
],
),
{"schema": "gn_permissions"},
)
[docs]
id_permission = db.Column(db.Integer, primary_key=True)
[docs]
id_role = db.Column(db.Integer, ForeignKey("utilisateurs.t_roles.id_role"))
[docs]
id_action = db.Column(db.Integer, ForeignKey(PermAction.id_action))
[docs]
id_module = db.Column(db.Integer, ForeignKey("gn_commons.t_modules.id_module"))
[docs]
id_object = db.Column(
db.Integer,
ForeignKey(PermObject.id_object),
default=select(PermObject.id_object).where(PermObject.code_object == "ALL"),
)
[docs]
role = db.relationship(User, backref=db.backref("permissions", cascade_backrefs=False))
[docs]
action = db.relationship(PermAction)
[docs]
module = db.relationship(TModules)
[docs]
object = db.relationship(PermObject)
[docs]
scope_value = db.Column(db.Integer, ForeignKey(PermScope.value), nullable=True)
[docs]
scope = db.relationship(PermScope)
[docs]
sensitivity_filter = db.Column(db.Boolean, server_default=sa.false(), nullable=False)
[docs]
availability = db.relationship(
PermissionAvailable,
backref=db.backref("permissions", overlaps="action, object, module"), # overlaps expected
overlaps="action, object, module", # overlaps expected
)
[docs]
filters_fields = {
"SCOPE": scope_value,
"SENSITIVITY": sensitivity_filter,
}
@staticmethod
[docs]
def __SCOPE_le__(a, b):
return b is None or (a is not None and a <= b)
@staticmethod
[docs]
def __SENSITIVITY_le__(a, b):
# False only if: A is False and b is True
return (not a) <= (not b)
@staticmethod
[docs]
def __default_le__(a, b):
return a == b or b is None
[docs]
def __le__(self, other):
"""
Return True if this permission is supersed by 'other' permission.
This requires all filters to be supersed by 'other' filters.
"""
for name, field in self.filters_fields.items():
# Get filter comparison function or use default comparison function
__le_fct__ = getattr(self, f"__{name}_le__", Permission.__default_le__)
self_value, other_value = getattr(self, field.name), getattr(other, field.name)
if not __le_fct__(self_value, other_value):
return False
return True
@property
[docs]
def filters(self):
filters = []
for name, field in self.filters_fields.items():
value = getattr(self, field.name)
if field.nullable:
if value is None:
continue
if field.type.python_type == bool:
if not value:
continue
filters.append(PermFilter(name, value))
return filters
[docs]
def has_other_filters_than(self, *expected_filters):
for flt in self.filters:
if flt.name not in expected_filters:
return True
return False
@qfilter(query=True)
[docs]
def nice_order(cls, **kwargs):
return _nice_order(cls, kwargs["query"])