diff --git a/ippisite/ippidb/models.py b/ippisite/ippidb/models.py index 2a6b08450130d3d0be8d1d5978d508805f0d3ddc..6a886aeac82eaa78fb95f1829fb467729df630d5 100644 --- a/ippisite/ippidb/models.py +++ b/ippisite/ippidb/models.py @@ -477,19 +477,17 @@ class PpiComplex(models.Model): return "PPI {}, Complex {} ({})".format(self.ppi, self.complex, self.cc_nb) -class ValidatedCompoundsManager(models.Manager): - """ - ValidatedCompoundManager filters only compounds from validated - contributions (or not coming from contributions) in the results - of the database query - """ - - def get_queryset(self): - return ( - super() - .get_queryset() - .exclude(compoundaction__ppi__contribution__validated=False) - ) +class CompoundsManager(models.Manager): + def for_user(self, current_user): + qs = self.get_queryset() + if current_user.is_anonymous: + qs = qs.exclude(compoundaction__ppi__contribution__validated=False) + elif not current_user.is_superuser: + qs = qs.exclude( + Q(compoundaction__ppi__contribution__validated=False), + ~Q(compoundaction__ppi__contribution__contributor=current_user), + ) + return qs class Compound(AutoFillableModel): @@ -497,8 +495,7 @@ class Compound(AutoFillableModel): Chemical compound """ - objects = models.Manager() - validated = ValidatedCompoundsManager() + objects = CompoundsManager() canonical_smile = models.TextField(verbose_name="Canonical Smiles", unique=True) is_macrocycle = models.BooleanField( diff --git a/ippisite/ippidb/tests.py b/ippisite/ippidb/tests.py index 5e4c8b7ffb9a66c6ffaf981bb5c2e912c604a726..3b0aa612f43d5e3cb5b18861c97374d835f8b3d4 100644 --- a/ippisite/ippidb/tests.py +++ b/ippisite/ippidb/tests.py @@ -3,6 +3,7 @@ iPPI-DB unit tests """ import re +from django.contrib.auth import get_user_model from django.core.management import call_command from django.test import TestCase from django.urls import reverse @@ -16,6 +17,9 @@ from .models import ( CompoundTanimoto, create_tanimoto, update_compound_cached_properties, + CompoundAction, + Ppi, + Contribution, ) from .models import DrugBankCompound, Protein from .utils import FingerPrinter, mol2smi, smi2mol, smi2inchi, smi2inchikey @@ -405,6 +409,150 @@ class QueryCompoundViewsTestCase(TestCase): self.assertEqual(response.status_code, 200) +def create_dummy_user(login, password, admin=False): + User = get_user_model() + if admin: + user = User.objects.create_superuser(username=login, email=f"{login}@ippidb.test", password=password) + else: + user = User.objects.create_user(username=login, email=f"{login}@ippidb.test", password=password) + return user + + +def create_dummy_contribution(compound_id, smiles, user, symmetry, validate=False): + c = create_dummy_compound(compound_id, smiles) + ppi = Ppi() + ppi.symmetry = symmetry + ppi.save() + ca = CompoundAction() + ca.nb_copy_compounds = 1 + ca.compound = c + ca.ppi = ppi + ca.save() + co = Contribution() + co.ppi = ppi + co.contributor = user + co.validated = validate + co.save() + return c + + +class QueryCompoundViewsAccessTestCase(TestCase): + """ + Test the visibility of compounds belonging to + validated or unvalidated contributions + The "visibility matrix" is the following: + ============================================================================ + |Validation status / User| Anonymous | Creator | Another user | Admin user | + | Unvalidated | No | Yes | No | Yes | + | Validated | Yes | Yes | Yes | Yes | + ============================================================================ + """ + + @classmethod + def setUpTestData(cls): + symmetry = models.Symmetry() + symmetry.code = "AS" + symmetry.description = "asymmetric" + symmetry.save() + # create contributor 1 + cls.user_c1 = create_dummy_user("contributor1", "test1") + # create contributor 2 + cls.user_c2 = create_dummy_user("contributor2", "test2") + # create admin + cls.user_cA = create_dummy_user("admin", "testA", True) + # WHAT ARE THE USE CASES TO BE TESTED? + # user is anonymous, contributor, another contributor, admin + # compound is not validated, validated + create_dummy_contribution(1, "CC", cls.user_c1, symmetry, False) + create_dummy_contribution(2, "CCC", cls.user_c1, symmetry, True) + call_command("lle_le") + call_command("pca") + + def test_compound_detail_unvalidated_unlogged(self): + """ + Unvalidated compound should not be visible + if unlogged + """ + url = reverse("compound_card", kwargs={"pk": 1}) + response = self.client.get(url) + self.assertEqual(response.status_code, 404) + + def test_compound_detail_unvalidated_logged_creator(self): + """ + Unvalidated compound should be visible + if logged as the creator + """ + url = reverse("compound_card", kwargs={"pk": 1}) + self.client.force_login(self.user_c1) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.client.logout() + + def test_compound_detail_unvalidated_logged_user(self): + """ + Unvalidated compound should not be visible + if logged as another user + """ + url = reverse("compound_card", kwargs={"pk": 1}) + self.client.force_login(self.user_c2) + response = self.client.get(url) + self.assertEqual(response.status_code, 404) + self.client.logout() + + def test_compound_detail_unvalidated_logged_admin(self): + """ + Unvalidated compound should be visible + if logged as an admin user + """ + url = reverse("compound_card", kwargs={"pk": 1}) + self.client.force_login(self.user_cA) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.client.logout() + + def test_compound_detail_validated_unlogged(self): + """ + Validated compound should be visible + if unlogged + """ + url = reverse("compound_card", kwargs={"pk": 2}) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + + def test_compound_detail_validated_logged_creator(self): + """ + Validated compound should be visible + if logged as the creator + """ + url = reverse("compound_card", kwargs={"pk": 2}) + self.client.force_login(self.user_c1) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.client.logout() + + def test_compound_detail_validated_logged_user(self): + """ + Validated compound should be visible + if logged as another user + """ + url = reverse("compound_card", kwargs={"pk": 2}) + self.client.force_login(self.user_c2) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.client.logout() + + def test_compound_detail_validated_logged_admin(self): + """ + Validated compound should be visible + if logged as an admin user + """ + url = reverse("compound_card", kwargs={"pk": 2}) + self.client.force_login(self.user_cA) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.client.logout() + + class TestGetDoiInfo(TestCase): """ Test retrieving information for a DOI entry diff --git a/ippisite/ippidb/views/compound_query.py b/ippisite/ippidb/views/compound_query.py index b49ebac42d3386bbc45e3aabf01ee256dad1b41a..38ea59b3b2e6aeef5e2e2ca7ec09aea8ce3cdb6d 100644 --- a/ippisite/ippidb/views/compound_query.py +++ b/ippisite/ippidb/views/compound_query.py @@ -561,19 +561,12 @@ class CompoundListView(ListView): return context def get_queryset(self): + # compounds can be accessed only if they are validated or + # if the current user is an admin OR their contributor + self.queryset = self.model.objects.for_user(self.request.user) self.filter_context = {} # get queryset qs = super().get_queryset() - # compounds can be accessed only if they are validated or - # if the current user is an admin OR their contributor - current_user = self.request.user - if current_user.is_anonymous: - qs = qs.exclude(compoundaction__ppi__contribution__validated=False) - elif not current_user.is_superuser: - qs = qs.exclude( - Q(compoundaction__ppi__contribution__validated=False), - ~Q(compoundaction__ppi__contribution__contributor=current_user), - ) # add filters self.filter_context[ "disabled" @@ -745,6 +738,9 @@ class CompoundDetailView(DetailView): model = Compound template_name = "compound_card.html" + def get_queryset(self): + return self.model.objects.for_user(self.request.user) + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["le_lle_biplot_data"] = LeLleBiplotData.objects.get().le_lle_biplot_data