Skip to content

Commit

Permalink
Add migration to handle layer tree
Browse files Browse the repository at this point in the history
  • Loading branch information
jrmi committed Dec 18, 2019
1 parent 86a911a commit c4382c1
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 18 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,6 @@ dmypy.json

# Pyre type checker
.pyre/

# IDE
.vscode
79 changes: 66 additions & 13 deletions terra_layer/migrations/0045_add_tree_2_scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,79 @@
class Migration(migrations.Migration):

dependencies = [
('terra_layer', '0044_order'),
("terra_layer", "0044_order"),
]

operations = [
migrations.AlterModelOptions(
name='layer',
options={'ordering': ('order', 'name')},
name="layer", options={"ordering": ("order", "name")},
),
migrations.AddField(
model_name='scene',
name='tree',
field=django.contrib.postgres.fields.jsonb.JSONField(default=list, validators=[terra_layer.schema.JSONSchemaValidator(limit_value={'$id': 'http://terralego.com/scene_layertree.json', '$schema': 'http://json-schema.org/draft-07/schema#', 'definitions': {}, 'items': {'$id': '#/items', 'dependencies': {'group': ['children', 'label']}, 'properties': {'children': {'$ref': '#'}, 'expanded': {'$id': '#/items/properties/expanded', 'default': False, 'examples': [True], 'title': 'The expanded status in admin. Not used yet', 'type': 'boolean'}, 'geolayer': {'$id': '#/items/properties/geolayer', 'default': 0, 'examples': [96], 'title': 'The geolayer id', 'type': 'integer'}, 'group': {'$id': '#/items/properties/group', 'default': False, 'examples': [True], 'title': "The group name. Present if it's a group.", 'type': 'boolean'}, 'label': {'$id': '#/items/properties/label', 'default': '', 'examples': ['My Group'], 'pattern': '^(.*)$', 'title': 'The group name', 'type': 'string'}}, 'required': [], 'title': 'Layer tree item', 'type': 'object'}, 'title': 'Scene layer tree schema', 'type': 'array'})]),
model_name="scene",
name="tree",
field=django.contrib.postgres.fields.jsonb.JSONField(
default=list,
validators=[
terra_layer.schema.JSONSchemaValidator(
limit_value={
"$id": "http://terralego.com/scene_layertree.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {},
"items": {
"$id": "#/items",
"dependencies": {"group": ["children", "label"]},
"properties": {
"children": {"$ref": "#"},
"expanded": {
"$id": "#/items/properties/expanded",
"default": False,
"examples": [True],
"title": "The expanded status in admin. Not used yet",
"type": "boolean",
},
"geolayer": {
"$id": "#/items/properties/geolayer",
"default": 0,
"examples": [96],
"title": "The geolayer id",
"type": "integer",
},
"group": {
"$id": "#/items/properties/group",
"default": False,
"examples": [True],
"title": "The group name. Present if it's a group.",
"type": "boolean",
},
"label": {
"$id": "#/items/properties/label",
"default": "",
"examples": ["My Group"],
"pattern": "^(.*)$",
"title": "The group name",
"type": "string",
},
},
"required": [],
"title": "Layer tree item",
"type": "object",
},
"title": "Scene layer tree schema",
"type": "array",
}
)
],
),
),
migrations.AlterField(
model_name='layer',
name='group',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='layers', to='terra_layer.LayerGroup'),
),
migrations.AlterUniqueTogether(
name='layergroup',
unique_together=set(),
model_name="layer",
name="group",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="layers",
to="terra_layer.LayerGroup",
),
),
migrations.AlterUniqueTogether(name="layergroup", unique_together=set(),),
]
87 changes: 87 additions & 0 deletions terra_layer/migrations/0046_migrate_layer_groups_2_tree.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Generated by Django 2.2.7 on 2019-12-12 10:29

from django.db import migrations, transaction


def layer2dict(layer):
return {"geolayer": layer.id, "label": layer.name}


def group2dict(group):
children = []

for sub_group in group.children.all():
children.append(group2dict(sub_group))

for sub_layer in group.layers.all():
children.append(layer2dict(sub_layer))

return {
"label": group.label,
"group": True,
"children": children,
"exclusive": group.exclusive,
"selectors": group.selectors,
"settings": group.settings,
}


@transaction.atomic
def group2scene_tree(apps, schema_editor):
Scene = apps.get_model("terra_layer", "Scene")
LayerGroup = apps.get_model("terra_layer", "LayerGroup")
Layer = apps.get_model("terra_layer", "Layer")

def tree2models(scene, current_node, parent=None, order=0):
"""
Copied and adapted from the one in `models.py` to avoid further evolutions.
"""

if not parent:
# Create a default unique parent group that is ignored at export
parent = LayerGroup.objects.create(view=scene, label="Root")

if isinstance(current_node, list):
for idx, child in enumerate(current_node):
tree2models(scene, current_node=child, parent=parent, order=idx)

elif "group" in current_node:
# Handle groups
group = parent.children.create(
view=scene,
label=current_node["label"],
exclusive=current_node.get("exclusive", False),
order=order,
)

if "children" in current_node:
tree2models(scene, current_node=current_node["children"], parent=group)

elif "geolayer" in current_node:
# Handle layers
layer = Layer.objects.get(pk=current_node["geolayer"])
layer.group = parent
layer.order = order
layer.save()

for scene in Scene.objects.all():
tree = []
for lg in LayerGroup.objects.filter(view=scene, parent=None):
tree.append(group2dict(lg))
# There is no sublayer at level 0 so no need to handle them
scene.tree = tree

scene.save()
scene.layer_groups.all().delete() # Clear all groups to generate brand new one
tree2models(scene, tree)


class Migration(migrations.Migration):

dependencies = [
("terra_layer", "0045_add_tree_2_scene"),
]

operations = [
migrations.RunPython(group2scene_tree),
]
17 changes: 17 additions & 0 deletions terra_layer/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,23 @@ def compare(self, a, b):
"default": False,
"examples": [True],
},
"selectors": {
"$id": "#/items/properties/selectors",
"type": ["array", "null"],
"title": "The selectors for this group",
},
"settings": {
"$id": "#/items/properties/settings",
"type": "object",
"title": "The settings of group",
},
"group": {
"$id": "#/items/properties/exclusive",
"type": "boolean",
"title": "Is the group exclusive ?",
"default": False,
"examples": [True],
},
"children": {"$ref": "#"},
},
},
Expand Down
12 changes: 7 additions & 5 deletions terra_layer/views/layers.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def get_serializer_class(self,):
return SceneDetailSerializer
return SceneListSerializer

def check_layer_status(self, current_node):
def check_layer_status(self, view_id, current_node):
"""
Check all layers in tree to valide existence and scene ownership.
Recursive process.
Expand All @@ -55,22 +55,24 @@ def check_layer_status(self, current_node):
)

# Is layer owned by another scene ?
if layer.group and layer.group.view != self:
if layer.group and layer.group.view.id != view_id:
raise ValidationError(
f"Layer {item['geolayer']} can't be stolen from another scene"
)
else:
# And we start with the new node
self.check_layer_status(item["children"])
self.check_layer_status(view_id, item["children"])

def perform_update(self, serializer):
if serializer.is_valid():
self.check_layer_status(serializer.validated_data.get("tree", []))
self.check_layer_status(
serializer.instance.id, serializer.validated_data.get("tree", [])
)
serializer.save()

def perform_create(self, serializer):
if serializer.is_valid():
self.check_layer_status(serializer.validated_data.get("tree", []))
self.check_layer_status(None, serializer.validated_data.get("tree", []))
serializer.save()


Expand Down

0 comments on commit c4382c1

Please sign in to comment.