diff --git a/ippisite/ippidb/tests/__init__.py b/ippisite/ippidb/tests/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/ippisite/ippidb/tests/test_activity_computation_and_storage_pIC50_2380_002_e-9.yaml b/ippisite/ippidb/tests/test_activity_computation_and_storage_pIC50_2380_002_e-9.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..842a1db3b1005febf7a9428c0043816b08c0011d
--- /dev/null
+++ b/ippisite/ippidb/tests/test_activity_computation_and_storage_pIC50_2380_002_e-9.yaml
@@ -0,0 +1,43 @@
+activity_tests:
+- cell_line_name: ''
+  compound_activity_results:
+  - activity_mol: 2380.002
+    activity_type: pIC50
+    activity_unit: 1e-9
+    compound_name: toto
+    modulation_type: I
+  is_primary: true
+  nb_active_compounds: 2
+  protein_bound_construct: F
+  protein_complex: '2'
+  test_modulation_type: I
+  test_name: test
+  test_type: BIOCH
+complex:
+- cc_nb: 1
+  complex_type: Partner
+  domain_pfam_acc: PF05053
+  ppc_copy_nb: 1
+  ppp_copy_nb_per_p: ''
+  uniprot_id: O00255
+- cc_nb: 1
+  complex_type: Bound
+  domain_pfam_acc: PF05965
+  ppc_copy_nb: 1
+  ppp_copy_nb_per_p: 1
+  uniprot_id: Q03164
+complexChoice: inhibited
+complexType: Inhib_Hetero2merAB
+compounds:
+- common_name: super
+  compound_name: toto
+  molecule_smiles: CCC
+diseases:
+- ;toto;1;
+- ;titi;sfddf;
+- ;tata;3232;
+family_name: Menin (PF05053)
+id_source: '15072770'
+in_silico: true
+pdb_id: 3u85
+source: PM
diff --git a/ippisite/ippidb/tests/test_activity_computation_and_storage_pIC50_4_9846073_e-6.yaml b/ippisite/ippidb/tests/test_activity_computation_and_storage_pIC50_4_9846073_e-6.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..0a2e61b2d45c324e1ce6c7f00592a3ce046abe87
--- /dev/null
+++ b/ippisite/ippidb/tests/test_activity_computation_and_storage_pIC50_4_9846073_e-6.yaml
@@ -0,0 +1,43 @@
+activity_tests:
+- cell_line_name: ''
+  compound_activity_results:
+  - activity_mol: 4.9846073
+    activity_type: pIC50
+    activity_unit: 1e-6
+    compound_name: toto
+    modulation_type: I
+  is_primary: true
+  nb_active_compounds: 2
+  protein_bound_construct: F
+  protein_complex: '2'
+  test_modulation_type: I
+  test_name: test
+  test_type: BIOCH
+complex:
+- cc_nb: 1
+  complex_type: Partner
+  domain_pfam_acc: PF05053
+  ppc_copy_nb: 1
+  ppp_copy_nb_per_p: ''
+  uniprot_id: O00255
+- cc_nb: 1
+  complex_type: Bound
+  domain_pfam_acc: PF05965
+  ppc_copy_nb: 1
+  ppp_copy_nb_per_p: 1
+  uniprot_id: Q03164
+complexChoice: inhibited
+complexType: Inhib_Hetero2merAB
+compounds:
+- common_name: super
+  compound_name: toto
+  molecule_smiles: CCC
+diseases:
+- ;toto;1;
+- ;titi;sfddf;
+- ;tata;3232;
+family_name: Menin (PF05053)
+id_source: '15072770'
+in_silico: true
+pdb_id: 3u85
+source: PM
diff --git a/ippisite/ippidb/tests/test_activity_computation_and_storage_pIC50_639_39406_e-3.yaml b/ippisite/ippidb/tests/test_activity_computation_and_storage_pIC50_639_39406_e-3.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..459d512dd9b0f3557f5bfb3e50dcaf0140eae7a0
--- /dev/null
+++ b/ippisite/ippidb/tests/test_activity_computation_and_storage_pIC50_639_39406_e-3.yaml
@@ -0,0 +1,43 @@
+activity_tests:
+- cell_line_name: ''
+  compound_activity_results:
+  - activity_mol: 639.39406
+    activity_type: pIC50
+    activity_unit: 1e-3
+    compound_name: toto
+    modulation_type: I
+  is_primary: true
+  nb_active_compounds: 2
+  protein_bound_construct: F
+  protein_complex: '2'
+  test_modulation_type: I
+  test_name: test
+  test_type: BIOCH
+complex:
+- cc_nb: 1
+  complex_type: Partner
+  domain_pfam_acc: PF05053
+  ppc_copy_nb: 1
+  ppp_copy_nb_per_p: ''
+  uniprot_id: O00255
+- cc_nb: 1
+  complex_type: Bound
+  domain_pfam_acc: PF05965
+  ppc_copy_nb: 1
+  ppp_copy_nb_per_p: 1
+  uniprot_id: Q03164
+complexChoice: inhibited
+complexType: Inhib_Hetero2merAB
+compounds:
+- common_name: super
+  compound_name: toto
+  molecule_smiles: CCC
+diseases:
+- ;toto;1;
+- ;titi;sfddf;
+- ;tata;3232;
+family_name: Menin (PF05053)
+id_source: '15072770'
+in_silico: true
+pdb_id: 3u85
+source: PM
diff --git a/ippisite/ippidb/tests/test_activity_computation_and_storage_pIC50_6_85_e-0.yaml b/ippisite/ippidb/tests/test_activity_computation_and_storage_pIC50_6_85_e-0.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..b61f094338e568128575ea14d577a93e7f9b7088
--- /dev/null
+++ b/ippisite/ippidb/tests/test_activity_computation_and_storage_pIC50_6_85_e-0.yaml
@@ -0,0 +1,43 @@
+activity_tests:
+- cell_line_name: ''
+  compound_activity_results:
+  - activity_mol: 6.85
+    activity_type: pIC50
+    activity_unit: 1e-0
+    compound_name: toto
+    modulation_type: I
+  is_primary: true
+  nb_active_compounds: 2
+  protein_bound_construct: F
+  protein_complex: '2'
+  test_modulation_type: I
+  test_name: test
+  test_type: BIOCH
+complex:
+- cc_nb: 1
+  complex_type: Partner
+  domain_pfam_acc: PF05053
+  ppc_copy_nb: 1
+  ppp_copy_nb_per_p: ''
+  uniprot_id: O00255
+- cc_nb: 1
+  complex_type: Bound
+  domain_pfam_acc: PF05965
+  ppc_copy_nb: 1
+  ppp_copy_nb_per_p: 1
+  uniprot_id: Q03164
+complexChoice: inhibited
+complexType: Inhib_Hetero2merAB
+compounds:
+- common_name: super
+  compound_name: toto
+  molecule_smiles: CCC
+diseases:
+- ;toto;1;
+- ;titi;sfddf;
+- ;tata;3232;
+family_name: Menin (PF05053)
+id_source: '15072770'
+in_silico: true
+pdb_id: 3u85
+source: PM
diff --git a/ippisite/ippidb/tests/test_basic_entry.yaml b/ippisite/ippidb/tests/test_basic_entry.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..c1baf06500ae82f25c3fa1063b0373433336c7f8
--- /dev/null
+++ b/ippisite/ippidb/tests/test_basic_entry.yaml
@@ -0,0 +1,43 @@
+activity_tests:
+- cell_line_name: ''
+  compound_activity_results:
+  - activity_mol: 6.85
+    activity_type: pIC50
+    activity_unit: 1e-6
+    compound_name: toto
+    modulation_type: I
+  is_primary: true
+  nb_active_compounds: 2
+  protein_bound_construct: F
+  protein_complex: '2'
+  test_modulation_type: I
+  test_name: test
+  test_type: BIOCH
+complex:
+- cc_nb: 1
+  complex_type: Partner
+  domain_pfam_acc: PF05053
+  ppc_copy_nb: 1
+  ppp_copy_nb_per_p: ''
+  uniprot_id: O00255
+- cc_nb: 1
+  complex_type: Bound
+  domain_pfam_acc: PF05965
+  ppc_copy_nb: 1
+  ppp_copy_nb_per_p: 1
+  uniprot_id: Q03164
+complexChoice: inhibited
+complexType: Inhib_Hetero2merAB
+compounds:
+- common_name: super
+  compound_name: toto
+  molecule_smiles: CCC
+diseases:
+- ;toto;1;
+- ;titi;sfddf;
+- ;tata;3232;
+family_name: Menin (PF05053)
+id_source: '15072770'
+in_silico: true
+pdb_id: 3u85
+source: PM
diff --git a/ippisite/ippidb/tests/test_complex_no_pfam.yaml b/ippisite/ippidb/tests/test_complex_no_pfam.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..03e6c2cf616df4f93a3aefccbeb06d7a7c47b682
--- /dev/null
+++ b/ippisite/ippidb/tests/test_complex_no_pfam.yaml
@@ -0,0 +1,40 @@
+activity_tests:
+- cell_line_name: ''
+  compound_activity_results:
+  - activity_mol: 6.85
+    activity_type: pIC50
+    activity_unit: 1e-3
+    compound_name: toto
+    modulation_type: I
+  is_primary: true
+  nb_active_compounds: 2
+  protein_bound_construct: F
+  protein_complex: '1'
+  test_modulation_type: I
+  test_name: test
+  test_type: BIOCH
+complex:
+- cc_nb: 1
+  complex_type: Bound
+  domain_pfam_acc: PF00400
+  ppc_copy_nb: 1
+  ppp_copy_nb_per_p: 1
+  uniprot_id: P61964
+- cc_nb: 1
+  complex_type: Partner
+  domain_pfam_acc: null
+  ppc_copy_nb: 1
+  ppp_copy_nb_per_p: ''
+  uniprot_id: Q03164
+complexChoice: inhibited
+complexType: Inhib_Hetero2merAB
+compounds:
+- common_name: super
+  compound_name: toto
+  molecule_smiles: C
+diseases: []
+family_name: WD40 (PF00400)
+id_source: '26958703'
+in_silico: true
+pdb_id: 3emh
+source: PM
diff --git a/ippisite/ippidb/tests/test_entry_28.yaml b/ippisite/ippidb/tests/test_entry_28.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..d22b2cb8274900a4497432bd1b2b68df806fe256
--- /dev/null
+++ b/ippisite/ippidb/tests/test_entry_28.yaml
@@ -0,0 +1,41 @@
+activity_tests:
+- cell_line_name: ''
+  compound_activity_results:
+  - activity_mol: 3.59
+    activity_type: pKd
+    activity_unit: 1e-9
+    compound_name: '2'
+    modulation_type: I
+  is_primary: true
+  nb_active_compounds: 1
+  protein_bound_construct: F
+  protein_complex: '1'
+  test_modulation_type: B
+  test_name: biolayer interferometry assay
+  test_type: BIOCH
+complex:
+- cc_nb: 1
+  complex_type: Partner
+  domain_pfam_acc: null
+  ppc_copy_nb: 1
+  ppp_copy_nb_per_p: 1
+  uniprot_id: Q60795
+- cc_nb: 1
+  complex_type: Bound
+  domain_pfam_acc: PF01344
+  ppc_copy_nb: 1
+  ppp_copy_nb_per_p: 1
+  uniprot_id: Q9Z2X8
+complexChoice: inhibited
+complexType: Inhib_Hetero2merAB
+compounds:
+- common_name: ''
+  compound_name: '2'
+  molecule_smiles: c1cc2c(cc1)c(ccc2N(S(=O)(=O)c1ccc(cc1)OC)CC(=O)O)N(S(=O)(=O)c1ccc(cc1)OC)CC(=O)O
+diseases:
+- ;cancer;MONDO:0004992;
+family_name: KEAP1 / NRF2
+id_source: '24512214 '
+in_vitro: true
+pdb_id: 3wn7
+source: PM
diff --git a/ippisite/ippidb/tests/test_simple_heterodimer.yaml b/ippisite/ippidb/tests/test_simple_heterodimer.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..c3c5f7c7e926a4ff22dea3dc30fe61645921323b
--- /dev/null
+++ b/ippisite/ippidb/tests/test_simple_heterodimer.yaml
@@ -0,0 +1,51 @@
+activity_tests:
+- cell_line_name: ''
+  compound_activity_results:
+  - activity_mol: 6.85
+    activity_type: pIC50
+    activity_unit: 1e-3
+    compound_name: 16d
+    modulation_type: I
+  is_primary: true
+  nb_active_compounds: 2
+  protein_bound_construct: F
+  protein_complex: '1'
+  test_modulation_type: I
+  test_name: test
+  test_type: BIOCH
+complex:
+- cc_nb: 1
+  complex_type: Bound
+  domain_pfam_acc: PF00400
+  ppc_copy_nb: 1
+  ppp_copy_nb_per_p: 1
+  uniprot_id: P61964
+- cc_nb: 1
+  complex_type: Partner
+  domain_pfam_acc: null
+  ppc_copy_nb: 1
+  ppp_copy_nb_per_p: ''
+  uniprot_id: Q03164
+complexChoice: inhibited
+complexType: Inhib_Hetero2merAB
+compounds:
+- common_name: officially 16d
+  compound_name: 16d
+  ligand_id: ABC
+  molecule_smiles: CN1CCN(CC1)C1=C(C=C(C=C1)C1=CC(=CC=C1)CN1CCOCC1)NC(=O)C1=CNC(C=C1C(F)(F)F)=O
+- common_name: officially 14a
+  compound_name: 14a
+  is_macrocycle: false
+  molecule_iupac: N-(2-(4-Methylpiperazin-1-yl)-5-nitrophenyl)-6-oxo-4-(trifluoromethyl)-1,6-dihydropyridine-3-carboxamide
+- common_name: stuck
+  compound_name: lili
+  is_macrocycle: false
+  molecule_smiles: C1CCC1
+diseases: []
+family_name: WD40 (PF00400)
+id_source: '26958703'
+in_silico: true
+in_vitro: true
+pdb_id: 3emh
+source: PM
+xray: true
diff --git a/ippisite/ippidb/tests/test_simple_stabilized_heterodimer.yaml b/ippisite/ippidb/tests/test_simple_stabilized_heterodimer.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..1d79dd1e8f4868957a4919195a9feecb6eae92fd
--- /dev/null
+++ b/ippisite/ippidb/tests/test_simple_stabilized_heterodimer.yaml
@@ -0,0 +1,44 @@
+activity_tests:
+- cell_line_name: ''
+  compound_activity_results:
+  - activity_mol: 0.25
+    activity_type: pIC50
+    activity_unit: 1e-6
+    compound_name: FC
+    modulation_type: S
+  is_primary: true
+  nb_active_compounds: 2
+  protein_bound_construct: F
+  protein_complex: '1'
+  test_modulation_type: I
+  test_name: pull down
+  test_type: BIOCH
+complex:
+- cc_nb: 1
+  complex_type: Bound
+  domain_pfam_acc: PF00244
+  ppc_copy_nb: 1
+  ppp_copy_nb_per_p: 1
+  uniprot_id: P31947
+- cc_nb: 1
+  complex_type: Bound
+  domain_pfam_acc: null
+  ppc_copy_nb: 1
+  ppp_copy_nb_per_p: 1
+  uniprot_id: P03372
+complexChoice: stabilized
+complexType: Stab_Hetero2merAB
+compounds:
+- common_name: fusicoccin
+  compound_name: FC
+  ligand_id: FSC
+  molecule_smiles: COC[C@H]1CC[C@H]2[C@@H](C)[C@@H](O)[C@H](O[C@H]3O[C@H](COC(C)(C)C=C)[C@@H](O)[C@H](OC(C)=O)[C@H]3O)C3=C(C[C@H](O)[C@]3(C)\C=C1/2)[C@H](C)COC(C)=O
+diseases:
+- ;breast cancer;MONDO:0007254;
+family_name: 14-3-3 / ER
+id_source: '23676274'
+in_cellulo: true
+in_vitro: true
+pdb_id: 4jc3
+source: PM
+xray: true
diff --git a/ippisite/ippidb/tests/test_stabilizer_204.yaml b/ippisite/ippidb/tests/test_stabilizer_204.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..59cd362476b8f5d6fe7f041faa8fec36b90a23c2
--- /dev/null
+++ b/ippisite/ippidb/tests/test_stabilizer_204.yaml
@@ -0,0 +1,40 @@
+source: PM
+id_source: '23808890'
+in_vitro: true
+xray: true
+pdb_id: 4ihl
+complexChoice: stabilized
+complexType: Stab_Hetero2merAB
+complex:
+  - cc_nb: 1
+    complex_type: Bound
+    domain_pfam_acc: PF00244
+    ppc_copy_nb: 1
+    ppp_copy_nb_per_p: 1
+    uniprot_id: P63104
+  - cc_nb: 1
+    complex_type: Bound
+    domain_pfam_acc: null
+    ppc_copy_nb: 1
+    ppp_copy_nb_per_p: 1
+    uniprot_id: P04049
+family_name: 14-3-3 / C-RAF
+diseases:
+  - ;melanoma;(MONDO_0005105);
+compounds:
+  - common_name: Cotylenin A
+    compound_name: Cotylenin A
+    molecule_smiles: COC[C@H]1O[C@H](O[C@H]2[C@H](O)[C@H](C)[C@@H]3CC[C@](O)(COC)C3=C[C@@]3(C)CCC(C(C)C)=C23)[C@H](O)[C@H]2O[C@H]3O[C@@]12O[C@@]3(C)[C@@H]1CO1
+activity_tests:
+- test_type: BIOCH
+  test_name: Fluorescence Polarization
+  protein_bound_construct: F
+  test_modulation_type: B
+  nb_active_compounds: 1
+  protein_complex: '1'
+  compound_activity_results:
+  - activity_mol: 20
+    activity_type: pKd
+    activity_unit: 1e-10
+    compound_name: Cotylenin A
+    modulation_type: S
\ No newline at end of file
diff --git a/ippisite/ippidb/tests/test_with_all_tests.yaml b/ippisite/ippidb/tests/test_with_all_tests.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..3b8fefd311af4176a2e92deb7d9f5cef535b9cd5
--- /dev/null
+++ b/ippisite/ippidb/tests/test_with_all_tests.yaml
@@ -0,0 +1,72 @@
+activity_tests:
+- cell_line_name: ''
+  compound_activity_results:
+  - activity_mol: 6.85
+    activity_type: pIC50
+    activity_unit: 1e-3
+    compound_name: toto
+    modulation_type: I
+  is_primary: true
+  nb_active_compounds: 2
+  protein_bound_construct: F
+  protein_complex: '2'
+  test_modulation_type: I
+  test_name: test
+  test_type: BIOCH
+complex:
+- cc_nb: 1
+  complex_type: Partner
+  domain_pfam_acc: PF05053
+  ppc_copy_nb: 1
+  ppp_copy_nb_per_p: ''
+  uniprot_id: O00255
+- cc_nb: 1
+  complex_type: Bound
+  domain_pfam_acc: PF05965
+  ppc_copy_nb: 1
+  ppp_copy_nb_per_p: 1
+  uniprot_id: Q03164
+complexChoice: inhibited
+complexType: Inhib_Hetero2merAB
+compounds:
+- common_name: super
+  compound_name: toto
+  molecule_smiles: CCC
+cytotox: true
+cytotox_tests:
+- cell_line_name: '1043SK '
+  compound_concentration: '2'
+  compound_cytotox_results:
+  - compound_name: toto
+    toxicity: 'True'
+  test_name: test
+diseases:
+- ''
+- ;titi;sfddf;
+- ;tutu;5456456;
+- ;tutu;5456456;
+- ''
+family_name: Menin (PF05053)
+id_source: '15072770'
+in_silico: true
+pdb_id: 3u85
+pharmacokinetic: true
+pharmacokinetic_tests:
+- administration_mode: IV
+  cell_line_name: '1043SK '
+  compound_pk_results:
+  - auc: '1'
+    c_max: '5.2'
+    clearance: '1.2'
+    compound_name: toto
+    oral_bioavailability: '22'
+    t_demi: '5'
+    t_max: '5'
+    tolerated: 'True'
+    voldistribution: '5.5'
+  concentration: '1.2'
+  dose: '3.5'
+  dose_interval: '2'
+  organism: 1
+  test_name: test
+source: PM
diff --git a/ippisite/ippidb/tests/tests_contribute.py b/ippisite/ippidb/tests/tests_contribute.py
index cd123dda54d8d512d8a3e4838a42199bdc60101d..5ecf48a3b84c3d319a703bbc6b52bce956c8bc9a 100644
--- a/ippisite/ippidb/tests/tests_contribute.py
+++ b/ippisite/ippidb/tests/tests_contribute.py
@@ -1,23 +1,15 @@
 """
 iPPI-DB contribution module tests
 """
-import math
-from decimal import Decimal
-from tempfile import NamedTemporaryFile
-
 from django.contrib.auth import get_user_model
 from django.contrib.auth.models import Permission
 from django.contrib.contenttypes.models import ContentType
-from django.db.models import Value
-from django.db.models.functions import Concat
 from django.test import TestCase
 from django.urls import reverse
 
 
 from ippidb import models
-from ippidb.admin import grant_contribution_permission
 from ippidb.forms import PDBForm
-from ippidb.ws import convert_iupac_to_smiles, get_uniprot_info
 from live_settings import live_settings
 
 import requests_cache
@@ -125,1046 +117,10 @@ class PDBFormTestCase(TestCase):
     def test_invalid_nouniprot(self):
         form = PDBForm({"pdb_id": "3wn8"})
         self.assertFalse(
-            form.is_valid(), "PDB code with no Uniprot mapping (3wn8) should be rejected"
+            form.is_valid(),
+            "PDB code with no Uniprot mapping (3wn8) should be rejected",
         )
         self.assertTrue(
             form.has_error("pdb_id", code="no_mapping"),
             "PDB code with no Uniprot mapping (3wn8) should be rejected with no_mappping error code",
         )
-
-
-class ContributionViewsTestCase(TestCase):
-    """
-    This class tests the Contribution Wizard by interacting with it
-    in the same way a user would do.
-    """
-
-    def setUp(self):
-        login = "contributor"
-        password = "12345"
-        User = get_user_model()
-        User.objects.create_user(username=login, password=password)
-        self.client.login(username=login, password=password)
-        grant_contribution_permission(None, None, User.objects.all())
-        symmetry = models.Symmetry()
-        symmetry.code = "AS"
-        symmetry.description = "asymmetric"
-        symmetry.save()
-        models.Taxonomy.objects.create(taxonomy_id=9606, name="Homo sapiens")
-
-    @staticmethod
-    def get_step_url(step_id):
-        step_url = reverse("admin-session-add")
-        if step_id is not None:
-            step_url += step_id + "/"
-        return step_url
-
-    def _process_contribution_wizard(self, entry_data):
-        def compute_ppi_name(entry_data):
-            bound_uniprots = [
-                item["uniprot_id"]
-                for item in entry_data["complex"]
-                if item["complex_type"] == "Bound"
-            ]
-            partner_uniprots = [
-                item["uniprot_id"]
-                for item in entry_data["complex"]
-                if item["complex_type"] == "Partner"
-            ]
-            bound_protein_names = [
-                get_uniprot_info(uniprot_id)["short_name"]
-                for uniprot_id in bound_uniprots
-            ]
-            partner_protein_names = [
-                get_uniprot_info(uniprot_id)["short_name"]
-                for uniprot_id in partner_uniprots
-            ]
-            bound_protein_names.sort()
-            partner_protein_names.sort()
-            bound_str = ",".join(bound_protein_names)
-            partner_str = ",".join(partner_protein_names)
-            name = bound_str
-            if partner_str != "":
-                name += " / " + partner_str
-            return name
-
-        future_expected_equals = [
-            (
-                models.Bibliography.objects.count,
-                models.Bibliography.objects.count() + 1,
-                "Bibliography count",
-            ),
-            (
-                models.Contribution.objects.count,
-                models.Contribution.objects.count() + 1,
-                "Contribution count",
-            ),
-            (
-                models.Compound.objects.count,
-                models.Compound.objects.count() + len(entry_data["compounds"]),
-                "Compounds count",
-            ),
-            (
-                models.Compound.objects.validated().count,
-                models.Compound.objects.validated().count(),
-                "Validated Compounds count should remains the same",
-            ),
-            (
-                models.CompoundAction.objects.count,
-                models.CompoundAction.objects.count() + len(entry_data["compounds"]),
-                "Compound Actions count",
-            ),
-            (
-                models.TestActivityDescription.objects.count,
-                models.TestActivityDescription.objects.count()
-                + len(entry_data.get("activity_tests", [])),
-                "Activity tests count",
-            ),
-            (
-                models.TestCytotoxDescription.objects.count,
-                models.TestCytotoxDescription.objects.count()
-                + len(entry_data.get("cytotox_tests", [])),
-                "Cytotox tests count",
-            ),
-            (
-                models.TestPKDescription.objects.count,
-                models.TestPKDescription.objects.count()
-                + len(entry_data.get("pharmacokinetic_tests", [])),
-                "Pharmacokinetic tests count",
-            ),
-            (
-                lambda: models.PpiFamily.objects.get().name,
-                entry_data.get("family_name"),
-                "Ppi family name",
-            ),
-            (
-                lambda: models.Ppi.objects.get().family.name,
-                entry_data.get("family_name"),
-                "Ppi family name from PPI",
-            ),
-            (
-                lambda: models.Ppi.objects.get().name,
-                compute_ppi_name(entry_data),
-                "Ppi name",
-            ),
-        ]
-        for activity_tests in entry_data["activity_tests"]:
-            for compound_activity_results in activity_tests[
-                "compound_activity_results"
-            ]:
-                if (
-                    "activity_mol" in compound_activity_results
-                    and compound_activity_results["activity_mol"] != ""
-                ):
-                    compound_activity_results["activity"] = -math.log10(
-                        Decimal(str(compound_activity_results["activity_mol"]))
-                        * Decimal(str(compound_activity_results["activity_unit"]))
-                    )
-
-        self._process_contribution_wizard_without_sanity_check(entry_data)
-
-        for fcn, results, msg in future_expected_equals:
-            self.assertEqual(fcn(), results, msg=msg)
-        post_validation_expected_equals = [
-            (
-                models.Compound.objects.validated().count,
-                models.Compound.objects.count(),
-                "Validated Compounds count should have increased",
-            )
-        ]
-        contribution_to_be_validated = models.Contribution.objects.get(validated=False)
-        contribution_to_be_validated.validated = True
-        contribution_to_be_validated.save()
-        for fcn, results, msg in post_validation_expected_equals:
-            self.assertEqual(fcn(), results, msg=msg)
-
-    def _process_contribution_wizard_without_sanity_check(
-        self, entry_data, error_expected_in_step=None
-    ):
-        """
-        The contribution add "wizard"
-        returns a 200
-        """
-        wizard_data = self._generate_wizard_data(entry_data)
-        for step in wizard_data:
-            # import json
-            # print(json.dumps(step, indent=4))
-            step_url = self.get_step_url(step.get("step-id"))
-            response = self.client.get(step_url)
-            # post form data
-            if step.get("form-data") is not None:
-                err_msg = (
-                    f"Response code not ok when getting form "
-                    f"for {step.get('step-id')} at {step_url}"
-                )
-                self.assertEqual(response.status_code, 200, err_msg)
-                form_data = {
-                    step["step-id"] + "-" + param_name: value
-                    for param_name, value in step.get("form-data").items()
-                }
-                form_data["ippi_wizard-current_step"] = step["step-id"]
-                response = self.client.post(step_url, form_data)
-            error_is_now = (
-                error_expected_in_step == step.get("step-id")
-                and response.status_code != 302
-            )
-            if (
-                response.status_code != 302
-                and step.get("step-id") != "done"
-                or error_is_now
-            ):
-                file_path = self.write_in_tmp_file(response)
-                err_msg = (
-                    f"Response code not ok when getting form for "
-                    f"{step.get('step-id')} at {step_url}. Server"
-                    f" response is stored in {file_path}"
-                )
-                if error_is_now:
-                    self.assertEqual(response.status_code, 200, err_msg)
-                    return
-                self.assertEqual(response.status_code, 302, err_msg)
-            # check redirect to next step (if there is one)
-            if step.get("next") is not None:
-                redirect_url = self.get_step_url(step.get("next"))
-                self.assertEqual(
-                    response.url,
-                    redirect_url,
-                    f"wrong redirection URL after step {step['step-id']}",
-                )
-        self.assertEqual(
-            response.url,
-            reverse("contribution-detail", kwargs={"contribution_pk": 1}),
-            f"wrong final URL, should be the contribution permanent URL",
-        )
-
-    def _generate_wizard_data(self, entry_data):
-        """
-        Generate the wizard form data dynamically based on iPPI-DB entry data
-        """
-
-        def get_id_form():
-            return {
-                "source": entry_data["source"],
-                "id_source": entry_data["id_source"],
-            }
-
-        def get_bibliography_form():
-            ret = {
-                "source": entry_data["source"],
-                "id_source": entry_data["id_source"],
-                "title": "Opportunistic+amoebae:+challenges"
-                "+in+prophylaxis+and+treatment.",
-                "journal_name": "Drug+resistance+updates+:+reviews+and+"
-                "commentaries+in+antimicrobial+and+anticancer"
-                "+chemotherapy",
-                "authors_list": "Schuster+FL,+Visvesvara+GS",
-                "biblio_year": "2004",
-            }
-            for k in [
-                "cytotox",
-                "xray",
-                "in_silico",
-                "in_vitro",
-                "in_cellulo",
-                "in_vivo",
-                "pharmacokinetic",
-            ]:
-                if entry_data.get(k, False):
-                    ret[k] = "on"
-            return ret
-
-        def get_pdb_form():
-            return {"pdb_id": entry_data["pdb_id"]}
-
-        def get_complex_type_form():
-            return {
-                "complexType": entry_data["complexType"],
-                "complexChoice": entry_data["complexChoice"],
-            }
-
-        def get_complex_form():
-            data = {
-                "TOTAL_FORMS": len(entry_data["complex"]),
-                "INITIAL_FORMS": len(entry_data["complex"]),
-                "MIN_NUM_FORMS": 0,
-                "MAX_NUM_FORMS": 1000,
-            }
-            for idx, entry_complex in enumerate(entry_data["complex"]):
-                data["{}-protein".format(idx)] = models.Protein.objects.get(
-                    uniprot_id=entry_complex["uniprot_id"]
-                ).id
-                data["{}-complex_type".format(idx)] = entry_complex["complex_type"]
-                if entry_complex["domain_pfam_acc"] is not None:
-                    try:
-                        data["{}-domain".format(idx)] = models.Domain.objects.get(
-                            pfam_acc=entry_complex["domain_pfam_acc"]
-                        ).id
-                    except models.Domain.DoesNotExist as dne:
-                        print(
-                            "No domain in DB for PFAM ACC {}".format(
-                                entry_complex["domain_pfam_acc"]
-                            )
-                        )
-                        raise dne
-                else:
-                    data["{}-domain".format(idx)] = ""
-                data["{}-ppc_copy_nb".format(idx)] = entry_complex["ppc_copy_nb"]
-                data["{}-cc_nb".format(idx)] = entry_complex["cc_nb"]
-                data["{}-ppp_copy_nb_per_p".format(idx)] = entry_complex[
-                    "ppp_copy_nb_per_p"
-                ]
-            return data
-
-        def get_ppi_form():
-            return {
-                "family": "",
-                "pdb_id": entry_data["pdb_id"],
-                "family_name": entry_data["family_name"],
-                "symmetry": 1,  # FIXME
-                "pockets_nb": 1,  # FIXME
-                "selected_diseases": "\n".join(entry_data["diseases"]),
-            }
-
-        def get_compound_form():
-            data = {
-                "TOTAL_FORMS": len(entry_data["compounds"]),
-                # FIXME there should be a way to list more than one compound
-                "INITIAL_FORMS": len(entry_data["compounds"]),
-                "MIN_NUM_FORMS": 1,
-                "MAX_NUM_FORMS": 1000,
-            }
-            for idx, compound_data_item in enumerate(entry_data["compounds"]):
-                data[f"{idx}-common_name"] = (compound_data_item["common_name"],)
-                data[f"{idx}-compound_name"] = (compound_data_item["compound_name"],)
-                if "molecule_smiles" in compound_data_item:
-                    data[f"{idx}-molecule_smiles"] = (
-                        compound_data_item["molecule_smiles"],
-                    )
-                elif "molecule_iupac" in compound_data_item:
-                    # if a IUPAC is provided for tests assume convert it to SMILES
-                    molecule_smiles = convert_iupac_to_smiles(
-                        compound_data_item["molecule_iupac"]
-                    )
-                    data[f"{idx}-molecule_smiles"] = molecule_smiles
-                data[f"{idx}-is_macrocycle"] = compound_data_item.get(
-                    "is_macrocycle", False
-                )
-            return data
-
-        def get_activity_description_form():
-            data = {
-                "TOTAL_FORMS": len(entry_data.get("activity_tests", [])),
-                "INITIAL_FORMS": 0,
-                "MIN_NUM_FORMS": 0,
-                "MAX_NUM_FORMS": 1000,
-            }
-            for idx, activity_test in enumerate(entry_data.get("activity_tests", [])):
-                data[f"{idx}-ppi"] = ""
-                data[f"{idx}-test_name"] = activity_test["test_name"]
-                data[f"{idx}-is_primary"] = activity_test["is_primary"]
-                data[f"{idx}-protein_bound_construct"] = activity_test[
-                    "protein_bound_construct"
-                ]
-                data[f"{idx}-test_type"] = activity_test["test_type"]
-                data[f"{idx}-test_modulation_type"] = activity_test[
-                    "test_modulation_type"
-                ]
-                data[f"{idx}-nb_active_compounds"] = activity_test[
-                    "nb_active_compounds"
-                ]
-                data[f"{idx}-cell_line_name"] = activity_test["cell_line_name"]
-                data[f"{idx}-protein_complex"] = activity_test["protein_complex"]
-                data[
-                    f"{idx}-compoundactivityresult_set-activity-results-TOTAL_FORMS"
-                ] = 1  # len(activity_test.get("compound_activity_results",[]))
-                data[
-                    f"{idx}-compoundactivityresult_set-activity-results-INITIAL_FORMS"
-                ] = 0
-                data[
-                    f"{idx}-compoundactivityresult_set-activity-results-MIN_NUM_FORMS"
-                ] = 0
-                data[
-                    f"{idx}-compoundactivityresult_set-activity-results-MAX_NUM_FORMS"
-                ] = 1000
-                for nidx, compound_activity_result in enumerate(
-                    activity_test["compound_activity_results"]
-                ):
-                    data[
-                        f"{idx}-compoundactivityresult_set-activity-results-{nidx}-compound_name"
-                    ] = compound_activity_result["compound_name"]
-                    data[
-                        f"{idx}-compoundactivityresult_set-activity-results-{nidx}-activity_type"
-                    ] = compound_activity_result["activity_type"]
-                    try:
-                        data[
-                            f"{idx}-compoundactivityresult_set-activity-results-{nidx}-activity_mol"
-                        ] = compound_activity_result["activity_mol"]
-                        data[
-                            f"{idx}-compoundactivityresult_set-activity-results-{nidx}-activity_unit"
-                        ] = compound_activity_result["activity_unit"]
-                    except KeyError:
-                        pass
-                    data[
-                        f"{idx}-compoundactivityresult_set-activity-results-{nidx}-modulation_type"
-                    ] = compound_activity_result["modulation_type"]
-                    # "id": "",
-                    # "test_activity_description": "",
-            return data
-
-        def get_cytotox_description_form():
-            data = {
-                "TOTAL_FORMS": len(entry_data.get("cytotox_tests", [])),
-                "INITIAL_FORMS": 0,
-                "MIN_NUM_FORMS": 1,
-                "MAX_NUM_FORMS": 1000,
-            }
-            for idx, cytotox_test in enumerate(entry_data.get("cytotox_tests", [])):
-                data[f"{idx}-test_name"] = cytotox_test["test_name"]
-                data[f"{idx}-compound_concentration"] = cytotox_test[
-                    "compound_concentration"
-                ]
-                data[f"{idx}-cell_line_name"] = cytotox_test["cell_line_name"]
-
-                data[
-                    f"{idx}-compoundcytotoxicityresult_set-cytotox-results-TOTAL_FORMS"
-                ] = len(cytotox_test.get("compound_cytotox_results", []))
-                data[
-                    f"{idx}-compoundcytotoxicityresult_set-cytotox-results-INITIAL_FORMS"
-                ] = 0
-                data[
-                    f"{idx}-compoundcytotoxicityresult_set-cytotox-results-MIN_NUM_FORMS"
-                ] = 0
-                data[
-                    f"{idx}-compoundcytotoxicityresult_set-cytotox-results-MAX_NUM_FORMS"
-                ] = 1000
-                for nidx, result in enumerate(cytotox_test["compound_cytotox_results"]):
-                    data[
-                        f"{idx}-compoundcytotoxicityresult_set-cytotox-results-{nidx}-compound_name"
-                    ] = result["compound_name"]
-                    data[
-                        f"{idx}-compoundcytotoxicityresult_set-cytotox-results-{nidx}-toxicity"
-                    ] = result["toxicity"]
-            return data
-
-        def get_pk_description_form():
-            data = {
-                "TOTAL_FORMS": len(entry_data.get("pharmacokinetic_tests", [])),
-                "INITIAL_FORMS": 0,
-                "MIN_NUM_FORMS": 1,
-                "MAX_NUM_FORMS": 1000,
-            }
-            for idx, pk_test in enumerate(entry_data.get("pharmacokinetic_tests", [])):
-                data[f"{idx}-test_name"] = pk_test["test_name"]
-                data[f"{idx}-organism"] = pk_test["organism"]
-                data[f"{idx}-administration_mode"] = pk_test["administration_mode"]
-                data[f"{idx}-concentration"] = pk_test["concentration"]
-                data[f"{idx}-dose"] = pk_test["dose"]
-                data[f"{idx}-dose_interval"] = pk_test["dose_interval"]
-                data[f"{idx}-cell_line_name"] = pk_test["cell_line_name"]
-
-                data[f"{idx}-compoundpkresult_set-pk-results-TOTAL_FORMS"] = len(
-                    pk_test.get("compound_pk_results", [])
-                )
-                data[f"{idx}-compoundpkresult_set-pk-results-INITIAL_FORMS"] = 0
-                data[f"{idx}-compoundpkresult_set-pk-results-MIN_NUM_FORMS"] = 0
-                data[f"{idx}-compoundpkresult_set-pk-results-MAX_NUM_FORMS"] = 1000
-                for nidx, result in enumerate(pk_test["compound_pk_results"]):
-                    data[
-                        f"{idx}-compoundpkresult_set-pk-results-{nidx}-tolerated"
-                    ] = result["tolerated"]
-                    data[f"{idx}-compoundpkresult_set-pk-results-{nidx}-auc"] = result[
-                        "auc"
-                    ]
-                    data[
-                        f"{idx}-compoundpkresult_set-pk-results-{nidx}-clearance"
-                    ] = result["clearance"]
-                    data[
-                        f"{idx}-compoundpkresult_set-pk-results-{nidx}-c_max"
-                    ] = result["c_max"]
-                    data[
-                        f"{idx}-compoundpkresult_set-pk-results-{nidx}-oral_bioavailability"
-                    ] = result["oral_bioavailability"]
-                    data[
-                        f"{idx}-compoundpkresult_set-pk-results-{nidx}-t_demi"
-                    ] = result["t_demi"]
-                    data[
-                        f"{idx}-compoundpkresult_set-pk-results-{nidx}-t_max"
-                    ] = result["t_max"]
-                    data[
-                        f"{idx}-compoundpkresult_set-pk-results-{nidx}-voldistribution"
-                    ] = result["voldistribution"]
-                    data[
-                        f"{idx}-compoundpkresult_set-pk-results-{nidx}-compound_name"
-                    ] = result["compound_name"]
-            return data
-
-        form_callables = {
-            None: lambda: None,
-            "IdForm": get_id_form,
-            "BibliographyForm": get_bibliography_form,
-            "PDBForm": get_pdb_form,
-            "ProteinDomainComplexTypeForm": get_complex_type_form,
-            "ProteinDomainComplexForm": get_complex_form,
-            "PpiForm": get_ppi_form,
-            "CompoundForm": get_compound_form,
-            "ActivityDescriptionFormSet": get_activity_description_form,
-        }
-        if entry_data.get("cytotox", False):
-            form_callables[
-                "TestCytotoxDescriptionFormSet"
-            ] = get_cytotox_description_form
-        if entry_data.get("pharmacokinetic", False):
-            form_callables["TestPKDescriptionFormSet"] = get_pk_description_form
-
-        # Adding a step for "done" in order to also save the data in the db
-        form_callables["done"] = lambda: None
-
-        form_ids = iter(list(form_callables.keys())[1:])
-        for step in form_callables.keys():
-            item = {
-                "step-id": step,
-                "form-data": form_callables[step](),
-                "next": next(form_ids, None),
-            }
-            yield item
-
-    def get_basic_entry(self):
-        """
-        Basic entry test
-        """
-        return {
-            "source": "PM",
-            "id_source": "15072770",
-            "in_silico": True,
-            "pdb_id": "3u85",
-            "diseases": [";toto;1;", ";titi;sfddf;", ";tata;3232;"],
-            "complexChoice": "inhibited",
-            "complexType": "Inhib_Hetero2merAB",
-            "complex": [
-                {
-                    "uniprot_id": "O00255",
-                    "complex_type": "Partner",
-                    "domain_pfam_acc": "PF05053",
-                    "ppc_copy_nb": 1,
-                    "cc_nb": 1,
-                    "ppp_copy_nb_per_p": "",
-                },
-                {
-                    "uniprot_id": "Q03164",
-                    "complex_type": "Bound",
-                    "domain_pfam_acc": "PF05965",
-                    "ppc_copy_nb": 1,
-                    "cc_nb": 1,
-                    "ppp_copy_nb_per_p": 1,
-                },
-            ],
-            "family_name": "Menin (PF05053)",
-            "compounds": [
-                {
-                    "molecule_smiles": "CCC",
-                    "compound_name": "toto",
-                    "common_name": "super",
-                }
-            ],
-            "activity_tests": [
-                {
-                    "test_name": "test",
-                    "is_primary": True,
-                    "protein_bound_construct": "F",
-                    "test_type": "BIOCH",
-                    "test_modulation_type": "I",
-                    "nb_active_compounds": 2,
-                    "cell_line_name": "",
-                    # means first in the list above
-                    "protein_complex": "2",
-                    "compound_activity_results": [
-                        {
-                            "compound_name": "toto",
-                            "activity_type": "pIC50",
-                            "activity_mol": 6.85,
-                            "activity_unit": "1e-6",
-                            "modulation_type": "I",
-                        }
-                    ],
-                }
-            ],
-        }
-
-    def test_basic_entry(self):
-        self._process_contribution_wizard(self.get_basic_entry())
-
-    def get_entry_28(self):
-        """
-        Test for the "contribution #28"
-        """
-        return {
-            "source": "PM",
-            "id_source": "24512214 ",
-            "in_vitro": True,
-            "pdb_id": "3wn7",
-            "diseases": ["cancer (MONDO:0004992)"],
-            # FIXME continue here
-            "complexChoice": "inhibited",
-            "complexType": "Inhib_Hetero2merAB",
-            "complex": [
-                {
-                    "uniprot_id": "Q60795",
-                    "complex_type": "Partner",
-                    "domain_pfam_acc": None,
-                    "ppc_copy_nb": 1,
-                    "cc_nb": 1,
-                    "ppp_copy_nb_per_p": 1,
-                },
-                {
-                    "uniprot_id": "Q9Z2X8",
-                    "complex_type": "Bound",
-                    "domain_pfam_acc": "PF01344",
-                    "ppc_copy_nb": 1,
-                    "cc_nb": 1,
-                    "ppp_copy_nb_per_p": 1,
-                },
-            ],
-            "family_name": "KEAP1 / NRF2",
-            "compounds": [
-                {
-                    "molecule_smiles": "c1cc2c(cc1)c(ccc2N(S(=O)(=O)c1ccc(cc1)"
-                    "OC)CC(=O)O)N(S(=O)(=O)c1ccc(cc1)OC)CC(=O)O",
-                    "compound_name": "2",
-                    "common_name": "",
-                }
-            ],
-            "activity_tests": [
-                {
-                    "test_name": "biolayer interferometry assay",
-                    "is_primary": True,
-                    "protein_bound_construct": "F",
-                    "test_type": "BIOCH",
-                    "test_modulation_type": "B",
-                    "nb_active_compounds": 1,
-                    "cell_line_name": "",
-                    # means first in the list above
-                    "protein_complex": "1",
-                    "compound_activity_results": [
-                        {
-                            "compound_name": "2",
-                            "activity_type": "pKd",
-                            "activity_mol": 3.59,
-                            "activity_unit": "1e-9",
-                            "modulation_type": "I",
-                        }
-                    ],
-                }
-            ],
-        }
-
-    def test_entry_28(self):
-        self._process_contribution_wizard(self.get_entry_28())
-
-    def _test_activity_computation_and_storage(self, value, power):
-        basic_entry = self.get_basic_entry()
-        for activity_tests in basic_entry["activity_tests"]:
-            for compound_activity_results in activity_tests[
-                "compound_activity_results"
-            ]:
-                compound_activity_results["activity_unit"] = "1e-%i" % power
-                try:
-                    del compound_activity_results["activity"]
-                except KeyError:
-                    pass
-        self._process_contribution_wizard(basic_entry)
-        bibliography = models.Bibliography.objects.get(
-            id_source=basic_entry["id_source"]
-        )
-        for activity_tests in basic_entry["activity_tests"]:
-            for compound_activity_results in activity_tests[
-                "compound_activity_results"
-            ]:
-                compound = models.RefCompoundBiblio.objects.get(
-                    bibliography=bibliography,
-                    compound_name=compound_activity_results["compound_name"],
-                ).compound
-                results = models.CompoundActivityResult.objects.get(
-                    activity_type=compound_activity_results["activity_type"],
-                    modulation_type=compound_activity_results["modulation_type"],
-                    compound=compound,
-                )
-                self.assertEqual(
-                    Decimal(compound_activity_results["activity"]).quantize(
-                        Decimal(10)
-                        ** -models.CompoundActivityResult._meta.get_field(
-                            "activity"
-                        ).decimal_places
-                    ),
-                    results.activity,
-                )
-
-    def test_activity_computation_and_storage(self):
-        self._test_activity_computation_and_storage(0.685, 0)
-
-    def test_activity_computation_and_storage_3(self):
-        self._test_activity_computation_and_storage(639.39406, 3)
-
-    def test_activity_computation_and_storage_6(self):
-        self._test_activity_computation_and_storage(4.9846073, 6)
-
-    def test_activity_computation_and_storage_9(self):
-        self._test_activity_computation_and_storage(2380.002, 9)
-
-    def test_activity_computation_and_storage_12(self):
-        self._test_activity_computation_and_storage(1.018, 12)
-
-    def test_no_activity_fails(self):
-        basic_entry = self.get_basic_entry()
-        for activity_tests in basic_entry["activity_tests"]:
-            for compound_activity_results in activity_tests[
-                "compound_activity_results"
-            ]:
-                compound_activity_results["activity_unit"] = ""
-                compound_activity_results["activity_mol"] = ""
-                try:
-                    del compound_activity_results["activity"]
-                except KeyError:
-                    pass
-        self._process_contribution_wizard_without_sanity_check(
-            basic_entry, error_expected_in_step="ActivityDescriptionFormSet",
-        )
-
-    def test_with_all_tests(self):
-        """
-        Basic entry test
-        """
-        entry_data = {
-            "source": "PM",
-            "id_source": "15072770",
-            "in_silico": True,
-            "pharmacokinetic": True,
-            "cytotox": True,
-            "pdb_id": "3u85",
-            "diseases": ["", ";titi;sfddf;", ";tutu;5456456;", ";tutu;5456456;", ""],
-            "complexChoice": "inhibited",
-            "complexType": "Inhib_Hetero2merAB",
-            "complex": [
-                {
-                    "uniprot_id": "O00255",
-                    "complex_type": "Partner",
-                    "domain_pfam_acc": "PF05053",
-                    "ppc_copy_nb": 1,
-                    "cc_nb": 1,
-                    "ppp_copy_nb_per_p": "",
-                },
-                {
-                    "uniprot_id": "Q03164",
-                    "complex_type": "Bound",
-                    "domain_pfam_acc": "PF05965",
-                    "ppc_copy_nb": 1,
-                    "cc_nb": 1,
-                    "ppp_copy_nb_per_p": 1,
-                },
-            ],
-            "family_name": "Menin (PF05053)",
-            "compounds": [
-                {
-                    "molecule_smiles": "CCC",
-                    "compound_name": "toto",
-                    "common_name": "super",
-                }
-            ],
-            "activity_tests": [
-                {
-                    "test_name": "test",
-                    "is_primary": True,
-                    "protein_bound_construct": "F",
-                    "test_type": "BIOCH",
-                    "test_modulation_type": "I",
-                    "nb_active_compounds": 2,
-                    "cell_line_name": "",
-                    # means first in the list above
-                    "protein_complex": "2",
-                    "compound_activity_results": [
-                        {
-                            "compound_name": "toto",
-                            "activity_type": "pIC50",
-                            "activity_mol": 6.85,
-                            "activity_unit": "1e-3",
-                            "modulation_type": "I",
-                        }
-                    ],
-                }
-            ],
-            "pharmacokinetic_tests": [
-                {
-                    "test_name": "test",
-                    "organism": 1,
-                    "administration_mode": "IV",
-                    "concentration": "1.2",
-                    "dose": "3.5",
-                    "dose_interval": "2",
-                    "cell_line_name": "1043SK ",
-                    # means position in the list above
-                    "compound_pk_results": [
-                        {
-                            "tolerated": "True",
-                            "auc": "1",
-                            "clearance": "1.2",
-                            "c_max": "5.2",
-                            "oral_bioavailability": "22",
-                            "t_demi": "5",
-                            "t_max": "5",
-                            "voldistribution": "5.5",
-                            "compound_name": "toto",
-                        }
-                    ],
-                }
-            ],
-            "cytotox_tests": [
-                {
-                    "test_name": "test",
-                    "compound_concentration": "2",
-                    "cell_line_name": "1043SK ",
-                    "compound_cytotox_results": [
-                        {"compound_name": "toto", "toxicity": "True"}
-                    ],
-                }
-            ],
-        }
-        self._process_contribution_wizard(entry_data)
-        input_diseases = set(entry_data["diseases"])
-        input_diseases.remove("")
-        self.assertSetEqual(
-            input_diseases,
-            set(
-                models.Ppi.objects.get(pdb_id=entry_data["pdb_id"])
-                .diseases.annotate(
-                    raw=Concat(Value(";"), "name", Value(";"), "identifier", Value(";"))
-                )
-                .values_list("raw", flat=True)
-            ),
-        )
-
-    def test_complex_no_pfam(self):
-        """
-        Test that it is possible to not select any domain
-        for one of the partners in the complex
-        (see #77)
-        """
-        entry_data = {
-            "source": "PM",
-            "id_source": "26958703",
-            "in_silico": True,
-            "pdb_id": "3emh",
-            "diseases": [],
-            "complexChoice": "inhibited",
-            "complexType": "Inhib_Hetero2merAB",
-            "complex": [
-                {
-                    "uniprot_id": "P61964",
-                    "complex_type": "Bound",
-                    "domain_pfam_acc": "PF00400",
-                    "ppc_copy_nb": 1,
-                    "cc_nb": 1,
-                    "ppp_copy_nb_per_p": 1,
-                },
-                {
-                    "uniprot_id": "Q03164",
-                    "complex_type": "Partner",
-                    "domain_pfam_acc": None,
-                    "ppc_copy_nb": 1,
-                    "cc_nb": 1,
-                    "ppp_copy_nb_per_p": "",
-                },
-            ],
-            "family_name": "WD40 (PF00400)",
-            "compounds": [
-                {
-                    "molecule_smiles": "C",
-                    "compound_name": "toto",
-                    "common_name": "super",
-                }
-            ],
-            "activity_tests": [
-                {
-                    "test_name": "test",
-                    "is_primary": True,
-                    "protein_bound_construct": "F",
-                    "test_type": "BIOCH",
-                    "test_modulation_type": "I",
-                    "nb_active_compounds": 2,
-                    "cell_line_name": "",
-                    # means position in the list above
-                    "protein_complex": "1",
-                    "compound_activity_results": [
-                        {
-                            "compound_name": "toto",
-                            "activity_type": "pIC50",
-                            "activity_mol": 6.85,
-                            "activity_unit": "1e-3",
-                            "modulation_type": "I",
-                        }
-                    ],
-                }
-            ],
-        }
-        self._process_contribution_wizard(entry_data)
-
-    def test_simple_heterodimer(self):
-        """
-        Test provided for simple heterodimer
-        """
-        entry_data = {
-            "source": "PM",
-            "id_source": "26958703",
-            "in_silico": True,
-            "in_vitro": True,
-            "xray": True,
-            "pdb_id": "3emh",
-            "diseases": [],
-            "complexChoice": "inhibited",
-            "complexType": "Inhib_Hetero2merAB",
-            "complex": [
-                {
-                    "uniprot_id": "P61964",
-                    "complex_type": "Bound",
-                    "domain_pfam_acc": "PF00400",
-                    "ppc_copy_nb": 1,
-                    "cc_nb": 1,
-                    "ppp_copy_nb_per_p": 1,
-                },
-                {
-                    "uniprot_id": "Q03164",
-                    "complex_type": "Partner",
-                    "domain_pfam_acc": None,
-                    "ppc_copy_nb": 1,
-                    "cc_nb": 1,
-                    "ppp_copy_nb_per_p": "",
-                },
-            ],
-            "family_name": "WD40 (PF00400)",
-            "compounds": [
-                {
-                    "molecule_smiles": r"CN1CCN(CC1)C1=C(C=C(C=C1)C1=CC(=CC=C1)"
-                    r"CN1CCOCC1)NC(=O)C1=CNC(C=C1C(F)(F)F)=O",
-                    "compound_name": "16d",
-                    "common_name": "officially 16d",
-                    "ligand_id": "ABC",
-                },
-                {
-                    "molecule_iupac": "N-(2-(4-Methylpiperazin-1-yl)-5-nitrophenyl)"
-                    "-6-oxo-4-(trifluoromethyl)-1,6-dihydropyridine-3-carboxamide",
-                    "compound_name": "14a",
-                    "common_name": "officially 14a",
-                    "is_macrocycle": False,
-                },
-                {
-                    "molecule_smiles": "C1CCC1",
-                    "compound_name": "lili",
-                    "common_name": "stuck",
-                    "is_macrocycle": False,
-                },
-            ],
-            "activity_tests": [
-                {
-                    "test_name": "test",
-                    "is_primary": True,
-                    "protein_bound_construct": "F",
-                    "test_type": "BIOCH",
-                    "test_modulation_type": "I",
-                    "nb_active_compounds": 2,
-                    "cell_line_name": "",
-                    # means position in the list above
-                    "protein_complex": "1",
-                    "compound_activity_results": [
-                        {
-                            "compound_name": "16d",
-                            "activity_type": "pIC50",
-                            "activity_mol": 6.85,
-                            "activity_unit": "1e-3",
-                            "modulation_type": "I",
-                        }
-                    ],
-                }
-            ],
-        }
-
-        self._process_contribution_wizard(entry_data)
-
-    def test_simple_stabilized_heterodimer(self):
-        """
-        Test provided for stabilized heterodimer
-        """
-        entry_data = {
-            "source": "PM",
-            "id_source": "23676274",
-            "in_cellulo": True,
-            "in_vitro": True,
-            "xray": True,
-            "pdb_id": "4jc3",
-            "complexChoice": "stabilized",
-            "complexType": "Stab_Hetero2merAB",
-            "complex": [
-                {
-                    "uniprot_id": "P31947",
-                    "complex_type": "Bound",
-                    "domain_pfam_acc": "PF00244",
-                    "ppc_copy_nb": 1,
-                    "cc_nb": 1,
-                    "ppp_copy_nb_per_p": 1,
-                },
-                {
-                    "uniprot_id": "P03372",
-                    "complex_type": "Bound",
-                    "domain_pfam_acc": None,
-                    "ppc_copy_nb": 1,
-                    "cc_nb": 1,
-                    "ppp_copy_nb_per_p": 1,
-                },
-            ],
-            "family_name": "14-3-3 / ER",
-            "diseases": ["breast cancer, MONDO:0007254"],
-            "compounds": [
-                {
-                    "molecule_smiles": r"COC[C@H]1CC[C@H]2[C@@H](C)[C@@H](O)"
-                    r"[C@H](O[C@H]3O[C@H](COC(C)(C)C=C)[C@@H](O)[C@H](OC(C)=O)"
-                    r"[C@H]3O)C3=C(C[C@H](O)[C@]3(C)\C=C1/2)[C@H](C)COC(C)=O",
-                    "compound_name": "FC",
-                    "common_name": "fusicoccin",
-                    "ligand_id": "FSC",
-                }
-            ],
-            "activity_tests": [
-                {
-                    "test_name": "pull down",
-                    "is_primary": True,
-                    "protein_bound_construct": "F",
-                    "test_type": "BIOCH",
-                    "test_modulation_type": "I",
-                    "nb_active_compounds": 2,
-                    "cell_line_name": "",
-                    # means position in the list above
-                    "protein_complex": "1",
-                    "compound_activity_results": [
-                        {
-                            "compound_name": "FC",
-                            "activity_type": "pIC50",
-                            "activity_mol": 0.25,
-                            "activity_unit": "1e-6",
-                            "modulation_type": "S",
-                        }
-                    ],
-                }
-            ],
-        }
-
-        self._process_contribution_wizard(entry_data)
-
-    def write_in_tmp_file(self, response):
-        """
-        Write an HTTP response contents into a temporary file for debug
-
-        :param response: response that will be saved
-        :type respons: django.http.HttpResponse
-        :return: path to the temporary file created
-        :rtype: str
-        """
-        with NamedTemporaryFile(delete=False, suffix=".html") as f:
-            f.write(response.content)
-            return f.name
diff --git a/ippisite/ippidb/tests/tests_contribute_e2e.py b/ippisite/ippidb/tests/tests_contribute_e2e.py
new file mode 100644
index 0000000000000000000000000000000000000000..373fc4a9cf9f9f40efbbeba3335388fc58aa414e
--- /dev/null
+++ b/ippisite/ippidb/tests/tests_contribute_e2e.py
@@ -0,0 +1,600 @@
+"""
+iPPI-DB contribution module "end to end" tests
+"""
+import math
+import os
+from decimal import Decimal
+from tempfile import NamedTemporaryFile
+import yaml
+import glob
+
+from django.contrib.auth import get_user_model
+from django.db.models import Value
+from django.db.models.functions import Concat
+from django.test import TestCase
+from django.urls import reverse
+from parameterized import parameterized
+import requests_cache
+
+from ippidb import models
+from ippidb.admin import grant_contribution_permission
+from ippidb.ws import get_uniprot_info
+
+
+requests_cache.install_cache("tests_contribute_cache", backend="sqlite")
+
+
+def compute_ppi_name(entry_data):
+    """
+    return the expected PPI name from a given contribution entry_data
+    """
+    bound_uniprots = [
+        item["uniprot_id"]
+        for item in entry_data["complex"]
+        if item["complex_type"] == "Bound"
+    ]
+    partner_uniprots = [
+        item["uniprot_id"]
+        for item in entry_data["complex"]
+        if item["complex_type"] == "Partner"
+    ]
+    bound_protein_names = [
+        get_uniprot_info(uniprot_id)["short_name"]
+        for uniprot_id in bound_uniprots
+    ]
+    partner_protein_names = [
+        get_uniprot_info(uniprot_id)["short_name"]
+        for uniprot_id in partner_uniprots
+    ]
+    bound_protein_names.sort()
+    partner_protein_names.sort()
+    bound_str = ",".join(bound_protein_names)
+    partner_str = ",".join(partner_protein_names)
+    name = bound_str
+    if partner_str != "":
+        name += " / " + partner_str
+    return name
+
+
+class ContributionE2ETestCase(TestCase):
+    """
+    This class tests the Contribution Wizard by interacting with it
+    in the same way a user would do.
+    """
+
+    def setUp(self):
+        login = "contributor"
+        password = "12345"
+        User = get_user_model()
+        User.objects.create_user(username=login, password=password)
+        self.client.login(username=login, password=password)
+        grant_contribution_permission(None, None, User.objects.all())
+        symmetry = models.Symmetry()
+        symmetry.code = "AS"
+        symmetry.description = "asymmetric"
+        symmetry.save()
+        models.Taxonomy.objects.create(taxonomy_id=9606, name="Homo sapiens")
+
+    @staticmethod
+    def get_step_url(step_id):
+        step_url = reverse("admin-session-add")
+        if step_id is not None:
+            step_url += step_id + "/"
+        return step_url
+
+    def _process_contribution_wizard(self, entry_data):
+        future_expected_equals = [
+            (
+                models.Bibliography.objects.count,
+                models.Bibliography.objects.count() + 1,
+                "Bibliography count",
+            ),
+            (
+                models.Contribution.objects.count,
+                models.Contribution.objects.count() + 1,
+                "Contribution count",
+            ),
+            (
+                models.Compound.objects.count,
+                models.Compound.objects.count() + len(entry_data["compounds"]),
+                "Compounds count",
+            ),
+            (
+                models.Compound.objects.validated().count,
+                models.Compound.objects.validated().count(),
+                "Validated Compounds count should remains the same",
+            ),
+            (
+                models.CompoundAction.objects.count,
+                models.CompoundAction.objects.count() + len(entry_data["compounds"]),
+                "Compound Actions count",
+            ),
+            (
+                models.TestActivityDescription.objects.count,
+                models.TestActivityDescription.objects.count()
+                + len(entry_data.get("activity_tests", [])),
+                "Activity tests count",
+            ),
+            (
+                models.TestCytotoxDescription.objects.count,
+                models.TestCytotoxDescription.objects.count()
+                + len(entry_data.get("cytotox_tests", [])),
+                "Cytotox tests count",
+            ),
+            (
+                models.TestPKDescription.objects.count,
+                models.TestPKDescription.objects.count()
+                + len(entry_data.get("pharmacokinetic_tests", [])),
+                "Pharmacokinetic tests count",
+            ),
+            (
+                lambda: models.PpiFamily.objects.get().name,
+                entry_data.get("family_name"),
+                "Ppi family name",
+            ),
+            (
+                lambda: models.Ppi.objects.get().family.name,
+                entry_data.get("family_name"),
+                "Ppi family name from PPI",
+            ),
+            (
+                lambda: models.Ppi.objects.get().name,
+                compute_ppi_name(entry_data),
+                "Ppi name",
+            ),
+        ]
+        for activity_tests in entry_data["activity_tests"]:
+            for compound_activity_results in activity_tests[
+                "compound_activity_results"
+            ]:
+                if (
+                    "activity_mol" in compound_activity_results
+                    and compound_activity_results["activity_mol"] != ""
+                ):
+                    compound_activity_results["activity"] = -math.log10(
+                        Decimal(str(compound_activity_results["activity_mol"]))
+                        * Decimal(str(compound_activity_results["activity_unit"]))
+                    )
+
+        self._process_contribution_wizard_without_sanity_check(entry_data)
+
+        for fcn, results, msg in future_expected_equals:
+            self.assertEqual(fcn(), results, msg=msg)
+        post_validation_expected_equals = [
+            (
+                models.Compound.objects.validated().count,
+                models.Compound.objects.count(),
+                "Validated Compounds count should have increased",
+            )
+        ]
+        contribution_to_be_validated = models.Contribution.objects.get(validated=False)
+        contribution_to_be_validated.validated = True
+        contribution_to_be_validated.save()
+        for fcn, results, msg in post_validation_expected_equals:
+            self.assertEqual(fcn(), results, msg=msg)
+
+    def _process_contribution_wizard_without_sanity_check(
+        self, entry_data, error_expected_in_step=None
+    ):
+        """
+        The contribution add "wizard"
+        returns a 200
+        """
+        wizard_data = self._generate_wizard_data(entry_data)
+        for step in wizard_data:
+            step_url = self.get_step_url(step.get("step-id"))
+            response = self.client.get(step_url)
+            # post form data
+            if step.get("form-data") is not None:
+                err_msg = (
+                    f"Response code not ok when getting form "
+                    f"for {step.get('step-id')} at {step_url}"
+                )
+                self.assertEqual(response.status_code, 200, err_msg)
+                form_data = {
+                    step["step-id"] + "-" + param_name: value
+                    for param_name, value in step.get("form-data").items()
+                }
+                form_data["ippi_wizard-current_step"] = step["step-id"]
+                response = self.client.post(step_url, form_data)
+            error_is_now = (
+                error_expected_in_step == step.get("step-id")
+                and response.status_code != 302
+            )
+            if (
+                response.status_code != 302
+                and step.get("step-id") != "done"
+                or error_is_now
+            ):
+                file_path = self.write_in_tmp_file(response)
+                err_msg = (
+                    f"Response code not ok when getting form for "
+                    f"{step.get('step-id')} at {step_url}. Server"
+                    f" response is stored in {file_path}"
+                )
+                if error_is_now:
+                    self.assertEqual(response.status_code, 200, err_msg)
+                    return
+                self.assertEqual(response.status_code, 302, err_msg)
+            # check redirect to next step (if there is one)
+            if step.get("next") is not None:
+                redirect_url = self.get_step_url(step.get("next"))
+                self.assertEqual(
+                    response.url,
+                    redirect_url,
+                    f"wrong redirection URL after step {step['step-id']}",
+                )
+        self.assertEqual(
+            response.url,
+            reverse("contribution-detail", kwargs={"contribution_pk": 1}),
+            f"wrong final URL, should be the contribution permanent URL",
+        )
+
+    def _generate_wizard_data(self, entry_data):
+        """
+        Generate the wizard form data dynamically based on iPPI-DB entry data
+        """
+
+        def get_id_form():
+            return {
+                "source": entry_data["source"],
+                "id_source": entry_data["id_source"],
+            }
+
+        def get_bibliography_form():
+            ret = {
+                "source": entry_data["source"],
+                "id_source": entry_data["id_source"],
+                "title": "Opportunistic+amoebae:+challenges"
+                "+in+prophylaxis+and+treatment.",
+                "journal_name": "Drug+resistance+updates+:+reviews+and+"
+                "commentaries+in+antimicrobial+and+anticancer"
+                "+chemotherapy",
+                "authors_list": "Schuster+FL,+Visvesvara+GS",
+                "biblio_year": "2004",
+            }
+            for k in [
+                "cytotox",
+                "xray",
+                "in_silico",
+                "in_vitro",
+                "in_cellulo",
+                "in_vivo",
+                "pharmacokinetic",
+            ]:
+                if entry_data.get(k, False):
+                    ret[k] = "on"
+            return ret
+
+        def get_pdb_form():
+            return {"pdb_id": entry_data["pdb_id"]}
+
+        def get_complex_type_form():
+            return {
+                "complexType": entry_data["complexType"],
+                "complexChoice": entry_data["complexChoice"],
+            }
+
+        def get_complex_form():
+            data = {
+                "TOTAL_FORMS": len(entry_data["complex"]),
+                "INITIAL_FORMS": len(entry_data["complex"]),
+                "MIN_NUM_FORMS": 0,
+                "MAX_NUM_FORMS": 1000,
+            }
+            for idx, entry_complex in enumerate(entry_data["complex"]):
+                data["{}-protein".format(idx)] = models.Protein.objects.get(
+                    uniprot_id=entry_complex["uniprot_id"]
+                ).id
+                data["{}-complex_type".format(idx)] = entry_complex["complex_type"]
+                if entry_complex["domain_pfam_acc"] is not None:
+                    try:
+                        data["{}-domain".format(idx)] = models.Domain.objects.get(
+                            pfam_acc=entry_complex["domain_pfam_acc"]
+                        ).id
+                    except models.Domain.DoesNotExist as dne:
+                        print(
+                            "No domain in DB for PFAM ACC {}".format(
+                                entry_complex["domain_pfam_acc"]
+                            )
+                        )
+                        raise dne
+                else:
+                    data["{}-domain".format(idx)] = ""
+                data["{}-ppc_copy_nb".format(idx)] = entry_complex["ppc_copy_nb"]
+                data["{}-cc_nb".format(idx)] = entry_complex["cc_nb"]
+                data["{}-ppp_copy_nb_per_p".format(idx)] = entry_complex[
+                    "ppp_copy_nb_per_p"
+                ]
+            return data
+
+        def get_ppi_form():
+            return {
+                "family": "",
+                "pdb_id": entry_data["pdb_id"],
+                "family_name": entry_data["family_name"],
+                "symmetry": 1,  # FIXME
+                "pockets_nb": 1,  # FIXME
+                "selected_diseases": "\n".join(entry_data["diseases"]),
+            }
+
+        def get_compound_form():
+            data = {
+                "TOTAL_FORMS": len(entry_data["compounds"]),
+                # FIXME there should be a way to list more than one compound
+                "INITIAL_FORMS": len(entry_data["compounds"]),
+                "MIN_NUM_FORMS": 1,
+                "MAX_NUM_FORMS": 1000,
+            }
+            for idx, compound_data_item in enumerate(entry_data["compounds"]):
+                data[f"{idx}-common_name"] = (compound_data_item["common_name"],)
+                data[f"{idx}-compound_name"] = (compound_data_item["compound_name"],)
+                if "molecule_smiles" in compound_data_item:
+                    data[f"{idx}-molecule_smiles"] = (
+                        compound_data_item["molecule_smiles"],
+                    )
+                elif "molecule_iupac" in compound_data_item:
+                    # if a IUPAC is provided for tests assume convert it to SMILES
+                    # molecule_smiles = convert_iupac_to_smiles(
+                    #    compound_data_item["molecule_iupac"]
+                    # )
+                    data[f"{idx}-molecule_iupac"] = compound_data_item["molecule_iupac"]
+                data[f"{idx}-is_macrocycle"] = compound_data_item.get(
+                    "is_macrocycle", False
+                )
+                if "ligand_id" in compound_data_item:
+                    data[f"{idx}-ligand_id"] = compound_data_item["ligand_id"]
+            return data
+
+        def get_activity_description_form():
+            data = {
+                "TOTAL_FORMS": len(entry_data.get("activity_tests", [])),
+                "INITIAL_FORMS": 0,
+                "MIN_NUM_FORMS": 0,
+                "MAX_NUM_FORMS": 1000,
+            }
+            for idx, activity_test in enumerate(entry_data.get("activity_tests", [])):
+                data[f"{idx}-ppi"] = ""
+                data[f"{idx}-test_name"] = activity_test["test_name"]
+                data[f"{idx}-is_primary"] = activity_test.get("is_primary", False)
+                data[f"{idx}-protein_bound_construct"] = activity_test[
+                    "protein_bound_construct"
+                ]
+                data[f"{idx}-test_type"] = activity_test["test_type"]
+                data[f"{idx}-test_modulation_type"] = activity_test[
+                    "test_modulation_type"
+                ]
+                data[f"{idx}-nb_active_compounds"] = activity_test[
+                    "nb_active_compounds"
+                ]
+                if "cell_line_name" in activity_test:
+                    data[f"{idx}-cell_line_name"] = activity_test["cell_line_name"]
+                data[f"{idx}-protein_complex"] = activity_test["protein_complex"]
+                data[
+                    f"{idx}-compoundactivityresult_set-activity-results-TOTAL_FORMS"
+                ] = 1  # len(activity_test.get("compound_activity_results",[]))
+                data[
+                    f"{idx}-compoundactivityresult_set-activity-results-INITIAL_FORMS"
+                ] = 0
+                data[
+                    f"{idx}-compoundactivityresult_set-activity-results-MIN_NUM_FORMS"
+                ] = 0
+                data[
+                    f"{idx}-compoundactivityresult_set-activity-results-MAX_NUM_FORMS"
+                ] = 1000
+                for nidx, compound_activity_result in enumerate(
+                    activity_test["compound_activity_results"]
+                ):
+                    data[
+                        f"{idx}-compoundactivityresult_set-activity-results-{nidx}-compound_name"
+                    ] = compound_activity_result["compound_name"]
+                    data[
+                        f"{idx}-compoundactivityresult_set-activity-results-{nidx}-activity_type"
+                    ] = compound_activity_result["activity_type"]
+                    try:
+                        data[
+                            f"{idx}-compoundactivityresult_set-activity-results-{nidx}-activity_mol"
+                        ] = compound_activity_result["activity_mol"]
+                        data[
+                            f"{idx}-compoundactivityresult_set-activity-results-{nidx}-activity_unit"
+                        ] = compound_activity_result["activity_unit"]
+                    except KeyError:
+                        pass
+                    data[
+                        f"{idx}-compoundactivityresult_set-activity-results-{nidx}-modulation_type"
+                    ] = compound_activity_result["modulation_type"]
+                    # "id": "",
+                    # "test_activity_description": "",
+            return data
+
+        def get_cytotox_description_form():
+            data = {
+                "TOTAL_FORMS": len(entry_data.get("cytotox_tests", [])),
+                "INITIAL_FORMS": 0,
+                "MIN_NUM_FORMS": 1,
+                "MAX_NUM_FORMS": 1000,
+            }
+            for idx, cytotox_test in enumerate(entry_data.get("cytotox_tests", [])):
+                data[f"{idx}-test_name"] = cytotox_test["test_name"]
+                data[f"{idx}-compound_concentration"] = cytotox_test[
+                    "compound_concentration"
+                ]
+                data[f"{idx}-cell_line_name"] = cytotox_test["cell_line_name"]
+
+                data[
+                    f"{idx}-compoundcytotoxicityresult_set-cytotox-results-TOTAL_FORMS"
+                ] = len(cytotox_test.get("compound_cytotox_results", []))
+                data[
+                    f"{idx}-compoundcytotoxicityresult_set-cytotox-results-INITIAL_FORMS"
+                ] = 0
+                data[
+                    f"{idx}-compoundcytotoxicityresult_set-cytotox-results-MIN_NUM_FORMS"
+                ] = 0
+                data[
+                    f"{idx}-compoundcytotoxicityresult_set-cytotox-results-MAX_NUM_FORMS"
+                ] = 1000
+                for nidx, result in enumerate(cytotox_test["compound_cytotox_results"]):
+                    data[
+                        f"{idx}-compoundcytotoxicityresult_set-cytotox-results-{nidx}-compound_name"
+                    ] = result["compound_name"]
+                    data[
+                        f"{idx}-compoundcytotoxicityresult_set-cytotox-results-{nidx}-toxicity"
+                    ] = result["toxicity"]
+            return data
+
+        def get_pk_description_form():
+            data = {
+                "TOTAL_FORMS": len(entry_data.get("pharmacokinetic_tests", [])),
+                "INITIAL_FORMS": 0,
+                "MIN_NUM_FORMS": 1,
+                "MAX_NUM_FORMS": 1000,
+            }
+            for idx, pk_test in enumerate(entry_data.get("pharmacokinetic_tests", [])):
+                data[f"{idx}-test_name"] = pk_test["test_name"]
+                data[f"{idx}-organism"] = pk_test["organism"]
+                data[f"{idx}-administration_mode"] = pk_test["administration_mode"]
+                data[f"{idx}-concentration"] = pk_test["concentration"]
+                data[f"{idx}-dose"] = pk_test["dose"]
+                data[f"{idx}-dose_interval"] = pk_test["dose_interval"]
+                data[f"{idx}-cell_line_name"] = pk_test["cell_line_name"]
+
+                data[f"{idx}-compoundpkresult_set-pk-results-TOTAL_FORMS"] = len(
+                    pk_test.get("compound_pk_results", [])
+                )
+                data[f"{idx}-compoundpkresult_set-pk-results-INITIAL_FORMS"] = 0
+                data[f"{idx}-compoundpkresult_set-pk-results-MIN_NUM_FORMS"] = 0
+                data[f"{idx}-compoundpkresult_set-pk-results-MAX_NUM_FORMS"] = 1000
+                for nidx, result in enumerate(pk_test["compound_pk_results"]):
+                    data[
+                        f"{idx}-compoundpkresult_set-pk-results-{nidx}-tolerated"
+                    ] = result["tolerated"]
+                    data[f"{idx}-compoundpkresult_set-pk-results-{nidx}-auc"] = result[
+                        "auc"
+                    ]
+                    data[
+                        f"{idx}-compoundpkresult_set-pk-results-{nidx}-clearance"
+                    ] = result["clearance"]
+                    data[
+                        f"{idx}-compoundpkresult_set-pk-results-{nidx}-c_max"
+                    ] = result["c_max"]
+                    data[
+                        f"{idx}-compoundpkresult_set-pk-results-{nidx}-oral_bioavailability"
+                    ] = result["oral_bioavailability"]
+                    data[
+                        f"{idx}-compoundpkresult_set-pk-results-{nidx}-t_demi"
+                    ] = result["t_demi"]
+                    data[
+                        f"{idx}-compoundpkresult_set-pk-results-{nidx}-t_max"
+                    ] = result["t_max"]
+                    data[
+                        f"{idx}-compoundpkresult_set-pk-results-{nidx}-voldistribution"
+                    ] = result["voldistribution"]
+                    data[
+                        f"{idx}-compoundpkresult_set-pk-results-{nidx}-compound_name"
+                    ] = result["compound_name"]
+            return data
+
+        form_callables = {
+            None: lambda: None,
+            "IdForm": get_id_form,
+            "BibliographyForm": get_bibliography_form,
+            "PDBForm": get_pdb_form,
+            "ProteinDomainComplexTypeForm": get_complex_type_form,
+            "ProteinDomainComplexForm": get_complex_form,
+            "PpiForm": get_ppi_form,
+            "CompoundForm": get_compound_form,
+            "ActivityDescriptionFormSet": get_activity_description_form,
+        }
+        if entry_data.get("cytotox", False):
+            form_callables[
+                "TestCytotoxDescriptionFormSet"
+            ] = get_cytotox_description_form
+        if entry_data.get("pharmacokinetic", False):
+            form_callables["TestPKDescriptionFormSet"] = get_pk_description_form
+
+        # Adding a step for "done" in order to also save the data in the db
+        form_callables["done"] = lambda: None
+
+        form_ids = iter(list(form_callables.keys())[1:])
+        for step in form_callables.keys():
+            item = {
+                "step-id": step,
+                "form-data": form_callables[step](),
+                "next": next(form_ids, None),
+            }
+            yield item
+
+    @parameterized.expand(
+        [
+            entry_path
+            for entry_path in glob.glob(
+                os.path.join(os.path.dirname(__file__), "*.yaml")
+            )
+        ]
+    )
+    def test_entry(self, entry_path):
+        # load the test data
+        entry_data = yaml.load(open(entry_path, "r"), Loader=yaml.FullLoader)
+        # process the wizard
+        self._process_contribution_wizard(entry_data)
+        # test diseases
+        input_diseases = set(entry_data["diseases"])
+        input_diseases.discard("")
+        self.assertSetEqual(
+            input_diseases,
+            set(
+                models.Ppi.objects.get(pdb_id=entry_data["pdb_id"])
+                .diseases.annotate(
+                    raw=Concat(Value(";"), "name", Value(";"), "identifier", Value(";"))
+                )
+                .values_list("raw", flat=True)
+            ),
+        )
+        # test compounds
+        for c in entry_data["compounds"]:
+            if "molecule_smiles" in c:
+                comp = models.Compound.objects.get(canonical_smile=c["molecule_smiles"])
+            elif "molecule_iupac" in c:
+                comp = models.Compound.objects.get(iupac_name=c["molecule_iupac"])
+            # test ligand ID has been stored if the X ray data option is checked
+            if entry_data.get("xray", False) is True:
+                self.assertEqual(comp.ligand_id, c.get("ligand_id", None))
+        # test activity
+        bibliography = models.Bibliography.objects.get(
+            id_source=entry_data["id_source"].strip()
+        )
+        for activity_tests in entry_data["activity_tests"]:
+            for compound_activity_results in activity_tests[
+                "compound_activity_results"
+            ]:
+                compound = models.RefCompoundBiblio.objects.get(
+                    bibliography=bibliography,
+                    compound_name=compound_activity_results["compound_name"],
+                ).compound
+                results = models.CompoundActivityResult.objects.get(
+                    activity_type=compound_activity_results["activity_type"],
+                    modulation_type=compound_activity_results["modulation_type"],
+                    compound=compound,
+                )
+                self.assertEqual(
+                    Decimal(compound_activity_results["activity"]).quantize(
+                        Decimal(10)
+                        ** -models.CompoundActivityResult._meta.get_field(
+                            "activity"
+                        ).decimal_places
+                    ),
+                    results.activity,
+                )
+
+    def write_in_tmp_file(self, response):
+        """
+        Write an HTTP response contents into a temporary file for debug
+
+        :param response: response that will be saved
+        :type respons: django.http.HttpResponse
+        :return: path to the temporary file created
+        :rtype: str
+        """
+        with NamedTemporaryFile(delete=False, suffix=".html") as f:
+            f.write(response.content)
+            return f.name
diff --git a/ippisite/ippidb/tests/tests_utils.py b/ippisite/ippidb/tests/tests_utils.py
index bf59015d9be616de248045867607c9bda862589a..5c73f34e75dc2ebeb17c8b63d4eb9470b7da2a0b 100644
--- a/ippisite/ippidb/tests/tests_utils.py
+++ b/ippisite/ippidb/tests/tests_utils.py
@@ -6,7 +6,7 @@ import re
 from django.test import TestCase
 from openbabel import vectorUnsignedInt, OBFingerprint
 
-from .utils import FingerPrinter, mol2smi, smi2mol, smi2inchi, smi2inchikey, smi2sdf
+from ippidb.utils import FingerPrinter, mol2smi, smi2mol, smi2inchi, smi2inchikey, smi2sdf
 
 
 class MolSmiTestCase(TestCase):