diff --git a/django/applications/catmaid/control/project.py b/django/applications/catmaid/control/project.py index c207bdd598..74a77f9948 100644 --- a/django/applications/catmaid/control/project.py +++ b/django/applications/catmaid/control/project.py @@ -23,7 +23,8 @@ 'annotation': "An arbitrary annotation", 'stack_property': 'A property which a stack has', 'landmark': "A particular type of location", - "landmarkgroup": "A type of collection that groups landmarks" + "landmarkgroup": "A type of collection that groups landmarks", + 'volume': 'A region of space' } # All relations needed by the tracing system alongside their @@ -34,6 +35,7 @@ 'annotated_with': "Something is annotated by something else.", 'has_property': 'A thing which has an arbitrary property', 'close_to': 'Something is spatially in the neighborhood of something else', + 'model_of': "Marks something as a model of something else." } # All client datastores needed by the tracing system along their descriptions. diff --git a/django/applications/catmaid/control/tracing.py b/django/applications/catmaid/control/tracing.py index 943367b7d1..2762770099 100644 --- a/django/applications/catmaid/control/tracing.py +++ b/django/applications/catmaid/control/tracing.py @@ -28,7 +28,6 @@ needed_relations = { 'labeled_as': "Something is labeled by sth. else.", 'element_of': "A generic element-of relationship", - 'model_of': "Marks something as a model of something else.", 'presynaptic_to': "Something is presynaptic to something else.", 'postsynaptic_to': "Something is postsynaptic to something else.", 'abutting': "Two things abut against each other", diff --git a/django/applications/catmaid/migrations/0051_create_volume_class_and_instances.py b/django/applications/catmaid/migrations/0051_create_volume_class_and_instances.py new file mode 100644 index 0000000000..05d9bad89a --- /dev/null +++ b/django/applications/catmaid/migrations/0051_create_volume_class_and_instances.py @@ -0,0 +1,133 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.14 on 2018-07-20 14:27 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + +from catmaid.apps import get_system_user + + +def forwards(apps, schema_editor): + """Make sure all required class and relations are existing for all + projects. We can't use the regular model classes, but have to get + them through the migration system. + """ + from catmaid.control.project import validate_project_setup + + Class = apps.get_model('catmaid', 'Class') + Project = apps.get_model('catmaid', 'Project') + Relation = apps.get_model('catmaid', 'Relation') + User = apps.get_model('auth', 'User') + ClientDatastore = apps.get_model('catmaid', 'ClientDatastore') + + projects = Project.objects.all() + # If there are no projects, don't continue, because there is nothing to + # migrate. + if 0 == len(projects) or 0 == User.objects.count(): + return + + system_user = get_system_user(User) + for p in projects: + validate_project_setup(p.id, system_user.id, True, Class, Relation, ClientDatastore) + +forward_create_relations = """ + CREATE TABLE volume_class_instance ( + volume_id bigint NOT NULL, + class_instance_id integer NOT NULL + ) + INHERITS (relation_instance); + + -- Volume Class Instance constraints + ALTER TABLE ONLY volume_class_instance + ADD CONSTRAINT volume_class_instance_pkey PRIMARY KEY (id); + + ALTER TABLE ONLY volume_class_instance + ADD CONSTRAINT volume_class_instance_sa_id + FOREIGN KEY (volume_id) + REFERENCES catmaid_volume(id) DEFERRABLE INITIALLY DEFERRED; + + ALTER TABLE ONLY volume_class_instance + ADD CONSTRAINT volume_class_instance_id_fkey + FOREIGN KEY (class_instance_id) + REFERENCES class_instance(id) DEFERRABLE INITIALLY DEFERRED; + + WITH new_cis AS ( + INSERT INTO class_instance (user_id, project_id, name, class_id) + SELECT + v.user_id, + v.project_id, + v.name, + c.id + FROM catmaid_volume v + JOIN class c ON (c.project_id = v.project_id AND c.class_name = 'volume') + RETURNING id, user_id, project_id, name + ) + INSERT INTO volume_class_instance (user_id, project_id, relation_id, volume_id, class_instance_id) + SELECT + v.user_id, + v.project_id, + r.id, + v.id, + ci.id + FROM catmaid_volume v + JOIN relation r ON (r.project_id = v.project_id AND r.relation_name = 'model_of') + JOIN new_cis ci ON ( + ci.user_id = v.user_id AND + ci.project_id = v.project_id AND + ci.name = v.name + ); + + -- Create history tables + SELECT create_history_table('volume_class_instance'::regclass, 'edition_time', 'txid'); +""" + +backward_create_relations = """ + SELECT disable_history_tracking_for_table('volume_class_instance'::regclass, + get_history_table_name('volume_class_instance'::regclass)); + SELECT drop_history_table('volume_class_instance'::regclass); + + DROP TABLE volume_class_instance; + + DELETE FROM class_instance + USING class c + WHERE class_instance.class_id = c.id + AND c.class_name = 'volume'; + +""" + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('catmaid', '0050_ensure_system_user'), + ] + + operations = [ + migrations.RunPython(forwards, migrations.RunPython.noop), + migrations.RunSQL( + forward_create_relations, + backward_create_relations, + [ + migrations.CreateModel( + name='VolumeClassInstance', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('creation_time', models.DateTimeField(default=django.utils.timezone.now)), + ('edition_time', models.DateTimeField(default=django.utils.timezone.now)), + ('class_instance', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='catmaid.ClassInstance')), + ('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='catmaid.Project')), + ('relation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='catmaid.Relation')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ('volume', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='catmaid.Volume')), + ], + options={ + 'db_table': 'volume_class_instance', + }, + ), + ] + ), + ] diff --git a/django/applications/catmaid/models.py b/django/applications/catmaid/models.py index dc3ffd096e..73586bed13 100644 --- a/django/applications/catmaid/models.py +++ b/django/applications/catmaid/models.py @@ -719,6 +719,17 @@ class Volume(UserFocusedModel): geometry = spatial_models.GeometryField(dim=3, srid=0) +class VolumeClassInstance(UserFocusedModel): + # Repeat the columns inherited from 'relation_instance' + relation = models.ForeignKey(Relation, on_delete=models.CASCADE) + # Now new columns: + volume = models.ForeignKey(Volume, on_delete=models.CASCADE) + class_instance = models.ForeignKey(ClassInstance, on_delete=models.CASCADE) + + class Meta: + db_table = "volume_class_instance" + + class RegionOfInterest(UserFocusedModel): # Repeat the columns inherited from 'location' editor = models.ForeignKey(User, on_delete=models.CASCADE,