[schema] Make relations composite where needed (closes #3383893).
authorVladimir Popescu <vladimir.popescu@logilab.fr>
Mon, 20 Jan 2014 14:58:58 +0000
changeset 652 aa98f6e98be8
parent 651 eece52372752
child 653 3de64284bc26
[schema] Make relations composite where needed (closes #3383893).
migration/0.7.0_Any.py
schema.py
test/unittest_schema.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/migration/0.7.0_Any.py	Mon Jan 20 14:58:58 2014 +0000
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+# copyright 2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2013 CEA (Saclay, FRANCE), all rights reserved.
+# contact http://www.logilab.fr -- mailto:contact@logilab.fr
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with this program. If not, see <http://www.gnu.org/licenses/>.
+
+sync_schema_props_perms(('QuestionnaireRun', 'concerns', 'Subject'))
+sync_schema_props_perms(('QuestionnaireRun', 'concerns', 'Group'))
+
+sync_schema_props_perms(('Assessment', 'generates', 'QuestionnaireRun'))
+sync_schema_props_perms(('Assessment', 'generates', 'Scan'))
+sync_schema_props_perms(('Assessment', 'generates', 'GenomicMeasure'))
+
+sync_schema_props_perms(('ScoreValue', 'measure', 'QuestionnaireRun'))
+sync_schema_props_perms(('ScoreValue', 'measure', 'Scan'))
+sync_schema_props_perms(('ScoreValue', 'measure', 'GenomicMeasure'))
+
+sync_schema_props_perms(('QuestionnaireRun', 'related_study', 'Study'))
+sync_schema_props_perms(('Scan', 'related_study', 'Study'))
+sync_schema_props_perms(('GenomicMeasure', 'related_study', 'Study'))
+
+sync_schema_props_perms('external_resources')
+
+sync_schema_props_perms(('ColumnRef', 'assessment', 'Assessment'))
+
+sync_schema_props_perms('comments')
+sync_schema_props_perms('wiki')
--- a/schema.py	Fri Jan 03 14:01:01 2014 +0100
+++ b/schema.py	Mon Jan 20 14:58:58 2014 +0000
@@ -30,6 +30,7 @@
 
 
 # ProcessingRun -> Measure
+# TODO: Determine the status of ProcessingRun wrt. QuestionnaireRun, Scan, GenomicMeasure
 ProcessingRun.add_relation(SubjectRelation('QuestionnaireRun', cardinality='**'), name='inputs')
 ProcessingRun.add_relation(SubjectRelation('Scan', cardinality='**'), name='inputs')
 ProcessingRun.add_relation(SubjectRelation('GenomicMeasure', cardinality='**'), name='inputs')
@@ -39,28 +40,35 @@
 ProcessingRun.add_relation(SubjectRelation('GenomicMeasure', cardinality='**'), name='outputs')
 
 # Maesure -> Subject
-QuestionnaireRun.add_relation(SubjectRelation(('Subject', 'Group'), cardinality='1*', inlined=True), name='concerns')
-Scan.add_relation(SubjectRelation(('Subject', 'Group'), cardinality='1*', inlined=True), name='concerns')
-GenomicMeasure.add_relation(SubjectRelation(('Subject', 'Group'), cardinality='1*', inlined=True), name='concerns')
+QuestionnaireRun.add_relation(SubjectRelation(('Subject', 'Group'), cardinality='1*',
+                                              inlined=True, composite='object'), name='concerns')
+Scan.add_relation(SubjectRelation(('Subject', 'Group'), cardinality='1*', inlined=True,
+                                  composite='object'), name='concerns')
+GenomicMeasure.add_relation(SubjectRelation(('Subject', 'Group'), cardinality='1*', inlined=True,
+                                            composite='object'), name='concerns')
 
 # Assessment -> Measure
+# TODO: Determine the status of Assessment wrt. QuestionnaireRun, Scan, GenomicMeasure in 'uses'
 Assessment.add_relation(SubjectRelation('QuestionnaireRun', cardinality='**'), name='uses')
 Assessment.add_relation(SubjectRelation('Scan', cardinality='**'), name='uses')
 Assessment.add_relation(SubjectRelation('GenomicMeasure', cardinality='**'), name='uses')
 
-Assessment.add_relation(SubjectRelation('QuestionnaireRun', cardinality='?*'), name='generates')
-Assessment.add_relation(SubjectRelation('Scan', cardinality='?*'), name='generates')
-Assessment.add_relation(SubjectRelation('GenomicMeasure', cardinality='?*'), name='generates')
+Assessment.add_relation(SubjectRelation('QuestionnaireRun', cardinality='?*', composite='subject'), name='generates')
+Assessment.add_relation(SubjectRelation('Scan', cardinality='?*', composite='subject'), name='generates')
+Assessment.add_relation(SubjectRelation('GenomicMeasure', cardinality='?*', composite='subject'), name='generates')
 
 # Score -> Measure
-ScoreValue.add_relation(SubjectRelation('QuestionnaireRun', cardinality='?*'), name='measure')
-ScoreValue.add_relation(SubjectRelation('Scan', cardinality='?*'), name='measure')
-ScoreValue.add_relation(SubjectRelation('GenomicMeasure', cardinality='?*'), name='measure')
+ScoreValue.add_relation(SubjectRelation('QuestionnaireRun', cardinality='?*', composite='object'), name='measure')
+ScoreValue.add_relation(SubjectRelation('Scan', cardinality='?*', composite='object'), name='measure')
+ScoreValue.add_relation(SubjectRelation('GenomicMeasure', cardinality='?*', composite='object'), name='measure')
 
 # Use for filepath computation
-QuestionnaireRun.add_relation(SubjectRelation('Study', cardinality='1*', inlined=True), name='related_study')
-Scan.add_relation(SubjectRelation('Study', cardinality='1*', inlined=True), name='related_study')
-GenomicMeasure.add_relation(SubjectRelation('Study', cardinality='1*', inlined=True), name='related_study')
+QuestionnaireRun.add_relation(SubjectRelation('Study', cardinality='1*', inlined=True,
+                                              composite='object'), name='related_study')
+Scan.add_relation(SubjectRelation('Study', cardinality='1*', inlined=True,
+                                  composite='object'), name='related_study')
+GenomicMeasure.add_relation(SubjectRelation('Study', cardinality='1*', inlined=True,
+                                            composite='object'), name='related_study')
 QuestionnaireRun.add_relation(SubjectRelation('Study', cardinality='**'), name='other_studies')
 Scan.add_relation(SubjectRelation('Study', cardinality='**'), name='other_studies')
 GenomicMeasure.add_relation(SubjectRelation('Study', cardinality='**'), name='other_studies')
@@ -72,17 +80,22 @@
 
 # Measure -> ExternalResources
 # Questionnaire may have some specifications files
-Questionnaire.add_relation(SubjectRelation('ExternalResource', cardinality='*1'), name='external_resources')
+Questionnaire.add_relation(SubjectRelation('ExternalResource', cardinality='*1',
+                                           composite='subject'), name='external_resources')
 # Some questionnaire run could have specific results files (subject dependant)
-QuestionnaireRun.add_relation(SubjectRelation('ExternalResource', cardinality='*1'), name='external_resources')
-Scan.add_relation(SubjectRelation('ExternalResource', cardinality='*1'), name='external_resources')
-GenomicMeasure.add_relation(SubjectRelation('ExternalResource', cardinality='*1'), name='external_resources')
+QuestionnaireRun.add_relation(SubjectRelation('ExternalResource', cardinality='*1',
+                                              composite='subject'), name='external_resources')
+Scan.add_relation(SubjectRelation('ExternalResource', cardinality='*1',
+                                  composite='subject'), name='external_resources')
+GenomicMeasure.add_relation(SubjectRelation('ExternalResource', cardinality='*1',
+                                            composite='subject'), name='external_resources')
 
 # Various relations
 Questionnaire.add_relation(SubjectRelation('ScoreDefinition', cardinality='*?'), name='definitions')
 ScoreDefinition.add_relation(SubjectRelation('Question', cardinality='**'), name='used_by')
 AnatomicalRegion.add_relation(SubjectRelation('ScoreValue', cardinality='**'), name='concerned_by')
-ColumnRef.add_relation(SubjectRelation('Assessment', cardinality='1*', inlined=True), name='assessment')
+ColumnRef.add_relation(SubjectRelation('Assessment', cardinality='1*', inlined=True,
+                                       composite='object'), name='assessment')
 
 # Extra fields for CWUser
 
@@ -94,9 +107,11 @@
 class comments(RelationDefinition):
     subject = 'Comment'
     object = COMMENTED_ENTITIES
+    composite='object'
 
 # Links to "Wikis" / Cards
 class wiki(RelationDefinition):
     subject = WIKI_ENTITIES
     object = 'Card'
     cardinality = '**'
+    composite='subject'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/unittest_schema.py	Mon Jan 20 14:58:58 2014 +0000
@@ -0,0 +1,128 @@
+# copyright 2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr -- mailto:contact@logilab.fr
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with this program. If not, see <http://www.gnu.org/licenses/>.
+
+""" Schema test """
+
+from cubicweb.devtools.testlib import CubicWebTC
+
+
+class BrainomicsSchemaTC(CubicWebTC):
+    """ Test proper behavior with respect to the composite relations. """
+
+    def setup_database(self):
+        """ Several entities involving composite relations are created,
+            according to the schema.
+        """
+        req = self.request()
+        t_study = req.create_entity('Study', name=u'Test study',
+                                    data_filepath=u'test/data/filepath')
+        t_card = req.create_entity('Card', title=u'This is a card.',
+                                   reverse_wiki=t_study)
+        t_subj = req.create_entity('Subject', identifier=u'test_subj',
+                                   gender=u'unknown', handedness=u'mixed')
+        t_center = req.create_entity('Center', identifier=u'Test center ID',
+                                     name=u'Test center name')
+        t_assessment = req.create_entity('Assessment', identifier=u'Test assess ID',
+                                         related_study=t_study,
+                                         reverse_holds=t_center,
+                                         reverse_concerned_by=t_subj)
+        t_questionnaire = req.create_entity('Questionnaire', name=u'Test quiz',
+                                            identifier=u'test_quiz_id',
+                                            type=u'test')
+        t_qrun = req.create_entity('QuestionnaireRun', identifier=u'test_qrun_id',
+                                   user_ident=u'test_qrun_uident',
+                                   instance_of=t_questionnaire,
+                                   reverse_generates=t_assessment,
+                                   concerns=t_subj,
+                                   related_study=t_study)
+        t_ext_res = req.create_entity('ExternalResource', name=u'Test ext res',
+                                      filepath=u'test/ext/res/filepath',
+                                      related_study=t_study,
+                                      reverse_external_resources=t_qrun)
+        t_comm = req.create_entity('Comment', content=u'This is a test comment',
+                                   comments=t_qrun)
+        t_score_def = req.create_entity('ScoreDefinition', name=u'Test score def',
+                                        type=u'string')
+        t_score_val = req.create_entity('ScoreValue', definition=t_score_def,
+                                        text=u'test score val', measure=t_qrun)
+
+    def test_cleanup_on_qrun_delete(self):
+        """ Test that on QuestionnaireRun deletion, the Comment,
+            ExternalResource and ScoreValue are deleted, but the Subject,
+            Assessment and Study are not deleted.
+        """
+        req = self.request()
+        qrun_eid = req.execute('Any X WHERE X is QuestionnaireRun').get_entity(0, 0).eid
+        req.execute('Delete QuestionnaireRun X WHERE X eid %(qreid)s', {'qreid': qrun_eid})
+        self.commit()
+        db_comm = req.execute('Any X WHERE X is Comment')
+        if db_comm:
+            self.fail('The Comment was not deleted')
+        db_ext_res = req.execute('Any X WHERE X is ExternalResource')
+        if db_ext_res:
+            self.fail('The ExternalResource was not deleted')
+        db_score_val = req.execute('Any X WHERE X is ScoreValue')
+        if db_score_val:
+            self.fail('The ScoreValue was not deleted')
+        db_subj = req.execute('Any X WHERE X is Subject')
+        if not db_subj:
+            self.fail('The Subject was deleted')
+        db_assess = req.execute('Any X WHERE X is Assessment')
+        if not db_assess:
+            self.fail('The Assessment was deleted')
+        db_study = req.execute('Any X WHERE X is Study')
+        if not db_study:
+            self.fail('The Study was deleted')
+
+    def test_cleanup_on_study_delete(self):
+        """ Test that on Study deletion, the QuestionnaireRun and Card are deleted.
+        """
+        req = self.request()
+        study_eid = req.execute('Any X WHERE X is Study').get_entity(0, 0).eid
+        db_card = req.execute('Any X WHERE X is Card, X title "This is a card."')
+        if not db_card:
+            self.fail('The test Card was not founs')
+        req.execute('DELETE Study X WHERE X eid %(studeid)s', {'studeid': study_eid})
+        self.commit()
+        db_qrun = req.execute('Any X WHERE X is QuestionnaireRun')
+        if db_qrun:
+            self.fail('The QuestionnaireRun was not deleted')
+        db_card = req.execute('Any X WHERE X is Card, X title "This is a card."')
+        if db_card:
+            self.fail('The Card was not deleted')
+
+    def test_cleanup_on_assessment_delete(self):
+        """ Test that on Assessment deletion, the QuestionnaireRun is deleted, but
+            the Study is not deleted.
+        """
+        req = self.request()
+        assess_eid = req.execute('Any X WHERE X is Assessment').get_entity(0, 0).eid
+        db_qrun = req.execute('Any X WHERE X is QuestionnaireRun')
+        if not db_qrun:
+            self.fail('No QuestionnaireRun was found')
+        req.execute('DELETE Assessment X WHERE X eid %(aseid)s', {'aseid': assess_eid})
+        self.commit()
+        db_qrun = req.execute('Any X WHERE X is QuestionnaireRun')
+        if db_qrun:
+            self.fail('The QuestionnaireRun was not deleted')
+        db_study = req.execute('Any X WHERE X is Study')
+        if not db_study:
+            self.fail('The Study was deleted')
+
+
+if __name__ == '__main__':
+    from logilab.common.testlib import unittest_main
+    unittest_main()