From ecf0680a0bb9e3718e93e914c55402492ed47fa4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Herv=C3=A9=20=20MENAGER?= <herve.menager@pasteur.fr>
Date: Thu, 18 May 2017 13:54:27 +0200
Subject: [PATCH] multiple model modifications

- separate Ppi into Ppi and PpiComplex classes (fixes #36)
- replace ActivityType table with an enumerated type
- remove complex from foreign keys when not required
---
 ippisite/db.sqlite3                           | Bin 569344 -> 569344 bytes
 ippisite/ippidb/forms.py                      |  10 ++-
 .../migrations/0010_auto_20170518_1142.py     |  68 ++++++++++++++++++
 ippisite/ippidb/models.py                     |  36 +++++++---
 4 files changed, 102 insertions(+), 12 deletions(-)
 create mode 100644 ippisite/ippidb/migrations/0010_auto_20170518_1142.py

diff --git a/ippisite/db.sqlite3 b/ippisite/db.sqlite3
index d0f0db2af8cd80bce56b7148c24a74b09675dec6..585c05bac9b5bb713a7f404b36981aaa02f2ba17 100644
GIT binary patch
delta 3966
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
VuhQTC=mTp@Hv_<jim!x#`G4x{>8$_&

delta 2739
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%0Dk!_%0a<kaBESKb8GzS1hw(v!;jCQ
z#V{eL9fPU(&J@z&&T}Y7y((&!9kj$9;OUp3UKW3TmLx563}(utcM2tI!evp?ilq6r
z$@8_j?1iLUSH<VUe>{gQXpS{ElR-*|M7#3G8=bWCNNUss_50i)vRj_}_!QEjqCj!L
zSy*Vt?@Xb#c`EP*>P(db=8vv`dbgWCR4V7F^p$|Wd;?|D)TkV~zl=dBubQccg?P_2
z%0!9z@$DmEIsSGOz(om`w4y}L;*-<p7g|%8i7towaw{FqiKjqDqNJZRp!qKWYD5n5
zyOT&G*Ld?J(yLK)nPd;bmbiU-aiQH73=}#4$9~_uIZ7yD?<C5=@C@QKVHjO1J2J-|
z!6%&oYbKF%j#xj5zRE2~Hxq>)*29}XJpo#U7`Ox#Ns3gB1H<dkJW(!a&@*8>OMx<v
zc&TAYY{Spsg<MJZk}wNeA6sXIkvsg;5UJ5~7A2{sBLv0>942sxz(E3o1cC%S1WE~X
z6R04i{SJS+7P8rN52gD|m(CSyO4J)v7gZGeKzW`0oS9HuQ>4&6r1IP}ZaM*{Vmn6Q
M2bx%CEldah0fhxfpa1{>

diff --git a/ippisite/ippidb/forms.py b/ippisite/ippidb/forms.py
index d7364dac..d7ef84c1 100644
--- a/ippisite/ippidb/forms.py
+++ b/ippisite/ippidb/forms.py
@@ -1,7 +1,7 @@
 from django.forms import ModelForm
 from django import forms
 from django.db import models
-from .models import Bibliography, Protein, ProteinDomainComplex, Ppi
+from .models import Bibliography, Protein, ProteinDomainComplex, Ppi, PpiComplex
 
 class IdForm(forms.Form):
 	id = forms.CharField(label="",max_length=100, widget=forms.TextInput(attrs={'placeholder': 'PubMed ID / DOI / Patent ID'}))
@@ -24,8 +24,12 @@ class ProteinDomainComplexForm(ModelForm):
 		model = ProteinDomainComplex
 		fields = ['protein', 'domain', 'ppc_copy_nb']
 
-
 class PpiForm(ModelForm):
 	class Meta:
 		model = Ppi
-		fields = ['complex', 'cc_nb', 'pdb_id', 'symmetry']
+		fields = ['pdb_id', 'symmetry']
+
+class PpiComplexForm(ModelForm):
+	class Meta:
+		model = PpiComplex
+		fields = ['complex', 'cc_nb']
diff --git a/ippisite/ippidb/migrations/0010_auto_20170518_1142.py b/ippisite/ippidb/migrations/0010_auto_20170518_1142.py
new file mode 100644
index 00000000..520ae2dc
--- /dev/null
+++ b/ippisite/ippidb/migrations/0010_auto_20170518_1142.py
@@ -0,0 +1,68 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.1 on 2017-05-18 11:42
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('ippidb', '0009_auto_20170517_2020'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='PpiComplex',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('cc_nb', models.IntegerField(default=1, verbose_name='Number of copies of the complex in the PPI')),
+                ('complex', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ippidb.ProteinDomainComplex')),
+            ],
+            options={
+                'verbose_name_plural': 'Ppi complexes',
+            },
+        ),
+        migrations.RemoveField(
+            model_name='ppi',
+            name='cc_nb',
+        ),
+        migrations.RemoveField(
+            model_name='ppi',
+            name='complex',
+        ),
+        migrations.RemoveField(
+            model_name='ppi',
+            name='ppi_id',
+        ),
+        migrations.RemoveField(
+            model_name='testactivitydescription',
+            name='complex',
+        ),
+        migrations.AlterField(
+            model_name='compoundactivityresult',
+            name='activity_type',
+            field=models.CharField(choices=[('pIC50', 'pIC50 (half maximal inhibitory concentration, -log10)'), ('pEC50', 'pEC50 (half maximal effective concentration, -log10)'), ('pKd', 'pKd (dissociation constant, -log10)'), ('pKi', 'pKi (inhibition constant, -log10)')], max_length=5, verbose_name='Activity type'),
+        ),
+        migrations.AlterUniqueTogether(
+            name='cmpdaction',
+            unique_together=set([('compound', 'activation_mode', 'pdb_id')]),
+        ),
+        migrations.RemoveField(
+            model_name='cmpdaction',
+            name='complex',
+        ),
+        migrations.AlterUniqueTogether(
+            name='cmpdaction',
+            unique_together=set([('ppi', 'compound', 'activation_mode', 'pdb_id')]),
+        ),
+        migrations.DeleteModel(
+            name='ActivityType',
+        ),
+        migrations.AddField(
+            model_name='ppicomplex',
+            name='ppi',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ippidb.Ppi'),
+        ),
+    ]
diff --git a/ippisite/ippidb/models.py b/ippisite/ippidb/models.py
index 226227f7..50288a93 100644
--- a/ippisite/ippidb/models.py
+++ b/ippisite/ippidb/models.py
@@ -150,12 +150,17 @@ class Symmetry(models.Model):
         return '{} ({})'.format(self.code, self.description)
 
 class Ppi(models.Model):
-    ppi_id = models.IntegerField('PPI identifier')
-    complex = models.ForeignKey(ProteinDomainComplex)
-    cc_nb = models.IntegerField('Number of copies of the complex in the PPI', default=1)
     pdb_id = models.CharField('PDB ID', max_length=4)
     symmetry = models.ForeignKey(Symmetry)
 
+class PpiComplex(models.Model):
+    ppi = models.ForeignKey(Ppi)
+    complex = models.ForeignKey(ProteinDomainComplex)
+    cc_nb = models.IntegerField('Number of copies of the complex in the PPI', default=1)
+
+    class Meta:
+        verbose_name_plural = "Ppi complexes"
+
 class Disease(models.Model):
     ppi = models.ForeignKey(Ppi)
     disease_name = models.CharField('Disease', max_length=30) # is there any database/nomenclature for diseases?
@@ -261,7 +266,6 @@ class TestActivityDescription(models.Model):
         ('I', 'Inhibition'),
         ('S', 'Stabilization')
     )
-    complex = models.ForeignKey(ProteinDomainBoundComplex)
     biblio = models.ForeignKey(Bibliography)
     ppi = models.ForeignKey(Ppi, blank=True, null=True)  
     test_name = models.CharField('Test name', max_length=100)  
@@ -270,8 +274,12 @@ class TestActivityDescription(models.Model):
     nb_active_compounds = models.IntegerField('Total number of active compounds')  
     cell_line = models.ForeignKey(CellLine)
 
-class ActivityType(models.Model):
-    name = models.CharField('Name', max_length=50, unique=True)
+    def get_complexes(self):
+        return None
+        # if test_modulation_type is Binding, return all bound complexes for the Ppi
+        # if test_modulation_type is Inhibition, return all Ppi complexes
+        # if test_modulation_type is Stabilization, return all bound complexes for the Ppi
+        # this should be added to the Ppi class as well
 
 class CompoundActivityResult(models.Model):
     MODULATION_TYPES = (
@@ -279,9 +287,15 @@ class CompoundActivityResult(models.Model):
         ('I', 'Inhibition'),
         ('S', 'Stabilization')
     )
+    ACTIVITY_TYPES = (
+        ('pIC50','pIC50 (half maximal inhibitory concentration, -log10)'),
+        ('pEC50','pEC50 (half maximal effective concentration, -log10)'),
+        ('pKd','pKd (dissociation constant, -log10)'),
+        ('pKi','pKi (inhibition constant, -log10)'),
+    )
     compound = models.ForeignKey(Compound)  
     test_activity_description = models.ForeignKey(TestActivityDescription)  
-    activity_type = models.ForeignKey(ActivityType)  
+    activity_type = models.CharField('Activity type', max_length=5, choices=ACTIVITY_TYPES)
     activity = models.DecimalField('Activity', max_digits=12, decimal_places=10)  
     modulation_type = models.CharField('Modulation type', max_length=1, choices=MODULATION_TYPES)  
 
@@ -334,7 +348,6 @@ class CmpdAction(models.Model):
         ('O', 'Orthosteric'),
         ('A', 'Allosteric')
     )
-    complex = models.ForeignKey(ProteinDomainBoundComplex)
     compound = models.ForeignKey(Compound)  
     activation_mode = models.CharField('Activation mode', max_length=1, choices=ACTIVATION_MODES)  
     ppi = models.ForeignKey(Ppi)
@@ -342,7 +355,12 @@ class CmpdAction(models.Model):
     nb_copy_compounds = models.IntegerField('Number of copies for the compound')  
 
     class Meta:
-        unique_together = (('complex', 'compound', 'pdb_id'),)
+        unique_together = (('ppi', 'compound', 'activation_mode', 'pdb_id'),)
+
+    def get_complexes(self):
+        return None
+        # return all bound complexes for the Ppi
+        # this should be added to the Ppi class as well
 
 class RefCompoundBiblio(models.Model):
     compound = models.ForeignKey(Compound)  
-- 
GitLab