Models
Related Issues |
Document Status |
---|---|
In Progress |
|
In Progress |
A prototypical implementation of the django based model system is available on https://gitlab.hzdr.de/model-data-explorer/django-psql-dag-test.
As you see from the Graph, the exact implementation is quite complicated due to the many relations between the different objects. But we’ll break it down in the following paragraphs.
DataGroup Relations
To efficiently mimic the relations between multiple DataGroups, we make use of so-called directed acyclic graphs implemented by the django-postgresql-dag library.
There are four main classes that we need to represant the relations between data groups:
DataGroup
The data group as a central object.
DataGroupNode
Each
DataGroup
holds a reference to multiple nodes, and each node represents one specific graph type that represents the permission system. There is a parent graph, a member graph and a list graph. See DataGroup Permission Graphs for more explanation.DataGroupUserGroup
Each
DataGroup
holds reference to multiple user groups, one group per role that a user can have in terms of a data group (see Users and data groups). This is mainly relevant for the parent and members graph, see below.DataGroupRelation
A relation between two data groups. Each
DataGroupRelation
is for one specific graph system (parent, member or list, see below). If an owner of the parent or child group requests a relation, such aDataGroupRelation
object is created. The owners of the other groups are then asked for approval. As soon as they approve, the connections between the two DataGroupNodes in the corresponding graph are made.
from django.db import models
from django_postgresql_dag.models import node_factory, edge_factory
from django.contrib.auth.models import User, Group
class DataGroupEdge(edge_factory("templateapp.DataGroupNode", concrete=False)):
def __str__(self):
return (
f"{self.parent.graph_type}: {self.parent.datagroup} "
"→ {self.child.datagroup}"
)
class DataGroupNode(node_factory(DataGroupEdge)):
"""A node to display relations between data groups."""
class Meta:
unique_together = ("graph_type", "datagroup")
class GraphType(models.TextChoices):
"""Type of the graph for a datagroup."""
parent_graph = "PARENT", "Parent-Child graph"
member_graph = "MEMBER", "Member inheritance graph"
list_graph = "LIST", "Dataset listing graph"
graph_type = models.CharField(max_length=10, choices=GraphType.choices)
datagroup = models.ForeignKey("DataGroup", on_delete=models.CASCADE)
class DataGroupUserGroup(Group):
"""A group with a certain role in a :class:`DataGroup`"""
class Roles(models.TextChoices):
owner = "OWNER", "owner priviliges"
user_manager = "USERMANAGER", "user manager privileges"
data_manager = "DATAMANAGER", "data manager privileges"
data_editor = "DATAEDITOR", "data editor privileges"
editor = "EDITOR", "editor privileges"
member = "MEMBER", "view permissions"
datagroup = models.ForeignKey("DataGroup", on_delete=models.CASCADE)
role = models.CharField(max_length=20, choices=Roles.choices)
class DataGroupRelation(models.Model):
"""An relation between two datagroups that awaits approval."""
class Meta:
unique_together = ("child_group", "parent_group", "graph_type")
child_group = models.ForeignKey(
"DataGroup", on_delete=models.CASCADE, related_name="%(class)s_child"
)
parent_group = models.ForeignKey(
"DataGroup", on_delete=models.CASCADE, related_name="%(class)s_parent"
)
child_approved = models.BooleanField(default=False)
parent_approved = models.BooleanField(default=False)
child_approved_by = models.ForeignKey(
User,
blank=True,
null=True,
on_delete=models.SET_NULL,
related_name="%(class)s_child_approval",
)
parent_approved_by = models.ForeignKey(
User,
blank=True,
null=True,
on_delete=models.SET_NULL,
related_name="%(class)s_parent_approval",
)
graph_type = models.CharField(
max_length=10, choices=DataGroupNode.GraphType.choices
)
class DataGroup(models.Model):
"""A DataGroup in the Model Data Explorer"""
name = models.CharField(max_length=100)
DataGroup Permission Graphs
The parent graph
Nodes in the parent graph inherit the owners, data managers, etc. from the parents. As an example, consider Hereon and the Institute for Coastal Systems.
Hereon has a node in the parent graph, and the institute does. To represent
the relation between the two, we set the DataGroupNode
in the parent graph
of Hereon as a parent for institutes DataGroupNode
in the parent graph.
As a consequence, each owner, data manager, etc. of Hereon will become an
owner, data manager, etc. of the institute.
The members graph
Nodes in the parent graph inherit members. But in contrast to the parent graph above, members of the child node are added as members of the parent node. As an example: If there is a relation in the members graph like Hereon ➔ Institute for Coastal Systems, then each member of the Institute for Coastal Systems will be automatically added as a member of Hereon.
The list graph
The list graph implements the permission to list the contents of a data group
as the result of another data group. If there is a relation
Hereon ➔ Institute for Coastal Systems between the two DataGroupNode
s
in the list graph, this means that
the institute is listed as a child on the Hereon page
each Dataset that grants list permissions to the institute is also listed on the Hereon page
Dataset relations
Relations between Dataset
s and DataGroup
s and between Dataset
s
and User
s are implemented more or less the same, as they both share the
same roles (see Users and datasets and
Datasets and data groups). Each relation needs to be approved by the
dataset owner and the related party (user or data group). Once the relation is
approved, the owner or corresponding DataGroupUserGroup
s get the relevant
permissions.
Four models are important here:
Dataset
The dataset that is related to the user or data group
DatasetUserRelation
A relation between a dataset and a user
DatasetDataGroupRelation
A relation between a dataset and a data group
from django.contrib.auth.models import User, Group
class DataGroup(models.Model):
"""A DataGroup in the Model Data Explorer"""
name = models.CharField(max_length=100)
class Dataset(models.Model):
"""A dataset in the model data explorer."""
name = models.CharField(max_length=100)
class DatasetRelationBase(models.Model):
"""Abstract base for a relation between dataset and user or group."""
class Meta:
unique_together = ("dataset", "related_party", "role")
abstract = True
class Roles(models.TextChoices):
owner = "OWNER", "owner priviliges"
data_manager = "DATAMANAGER", "data manager privileges"
data_editor = "DATAEDITOR", "data editor privileges"
editor = "EDITOR", "editor privileges"
member = "MEMBER", "view permissions"
dataset = models.ForeignKey(
"Dataset", on_delete=models.CASCADE, related_name="%(class)s"
)
role = models.CharField(
max_length=20, choices=Roles.choices
)
dataset_approved = models.BooleanField(default=False)
related_party_approved = models.BooleanField(default=False)
dataset_approved_by = models.ForeignKey(
User,
blank=True,
null=True,
on_delete=models.SET_NULL,
related_name="%(class)s_dataset_approval",
)
related_party_approved_by = models.ForeignKey(
User,
blank=True,
null=True,
on_delete=models.SET_NULL,
related_name="%(class)s_related_party_approval",
)
class DatasetDataGroupRelation(DatasetRelationBase):
"""A permission for a relation."""
related_party = models.ForeignKey(DataGroup, on_delete=models.CASCADE)
class DatasetUserRelation(DatasetRelationBase):
"""A permission for a relation."""
related_party = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="%(class)s_related_party"
)