diff --git a/terraso_backend/apps/graphql/schema/data_entries.py b/terraso_backend/apps/graphql/schema/data_entries.py index 783fbf599..a4add33b3 100644 --- a/terraso_backend/apps/graphql/schema/data_entries.py +++ b/terraso_backend/apps/graphql/schema/data_entries.py @@ -21,14 +21,14 @@ from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError from django.db import transaction -from django.db.models import Q +from django.db.models import Prefetch, Q, Subquery from graphene import relay from graphene_django import DjangoObjectType from apps.core.gis.parsers import parse_file_to_geojson from apps.core.models import Group, Landscape, Membership from apps.graphql.exceptions import GraphQLNotAllowedException, GraphQLNotFoundException -from apps.shared_data.models import DataEntry +from apps.shared_data.models import DataEntry, VisualizationConfig from apps.shared_data.models.data_entries import VALID_TARGET_TYPES from apps.shared_data.services import data_entry_upload_service @@ -97,24 +97,30 @@ class Meta: @classmethod def get_queryset(cls, queryset, info): user_pk = getattr(info.context.user, "pk", False) - user_groups_ids = Membership.objects.filter( - user__id=user_pk, membership_status=Membership.APPROVED - ).values_list("group", flat=True) - user_landscape_ids = Landscape.objects.filter( - associated_groups__group__memberships__user__id=user_pk, - associated_groups__group__memberships__membership_status=Membership.APPROVED, - associated_groups__is_default_landscape_group=True, - ).values_list("id", flat=True) + user_groups_ids = Subquery( + Group.objects.filter( + memberships__user__id=user_pk, memberships__membership_status=Membership.APPROVED + ).values("id") + ) + user_landscape_ids = Subquery( + Landscape.objects.filter( + associated_groups__group__memberships__user__id=user_pk, + associated_groups__group__memberships__membership_status=Membership.APPROVED, + associated_groups__is_default_landscape_group=True, + ).values("id") + ) - return queryset.filter( - Q( - shared_resources__target_content_type=ContentType.objects.get_for_model(Group), - shared_resources__target_object_id__in=user_groups_ids, - ) - | Q( - shared_resources__target_content_type=ContentType.objects.get_for_model(Landscape), - shared_resources__target_object_id__in=user_landscape_ids, - ) + return queryset.prefetch_related( + Prefetch( + "visualizations", + queryset=VisualizationConfig.objects.defer("configuration").prefetch_related( + "created_by" + ), + ), + "created_by", + ).filter( + Q(shared_resources__target_object_id__in=user_groups_ids) + | Q(shared_resources__target_object_id__in=user_landscape_ids) ) def resolve_url(self, info): diff --git a/terraso_backend/apps/graphql/schema/shared_resources.py b/terraso_backend/apps/graphql/schema/shared_resources.py index 844b41273..b13ecd65d 100644 --- a/terraso_backend/apps/graphql/schema/shared_resources.py +++ b/terraso_backend/apps/graphql/schema/shared_resources.py @@ -19,8 +19,10 @@ from apps.core.models import SharedResource -from . import DataEntryNode, GroupNode, LandscapeNode, VisualizationConfigNode +from . import GroupNode, LandscapeNode from .commons import TerrasoConnection +from .data_entries import DataEntryNode +from .visualization_config import VisualizationConfigNode class SourceNode(graphene.Union): diff --git a/terraso_backend/apps/graphql/schema/shared_resources_mixin.py b/terraso_backend/apps/graphql/schema/shared_resources_mixin.py index b989b5bed..86dffe189 100644 --- a/terraso_backend/apps/graphql/schema/shared_resources_mixin.py +++ b/terraso_backend/apps/graphql/schema/shared_resources_mixin.py @@ -51,4 +51,6 @@ class SharedResourcesMixin: ) def resolve_shared_resources(self, info, **kwargs): - return self.shared_resources + return self.shared_resources.prefetch_related( + "source", + ) diff --git a/terraso_backend/apps/graphql/schema/visualization_config.py b/terraso_backend/apps/graphql/schema/visualization_config.py index 6dd0fc0a9..d5d37d9d0 100644 --- a/terraso_backend/apps/graphql/schema/visualization_config.py +++ b/terraso_backend/apps/graphql/schema/visualization_config.py @@ -18,7 +18,7 @@ import structlog from django.contrib.contenttypes.models import ContentType from django.db import transaction -from django.db.models import Q +from django.db.models import Q, Subquery from graphene import relay from graphene_django import DjangoObjectType @@ -101,17 +101,30 @@ class Meta: @classmethod def get_queryset(cls, queryset, info): + # Only filter for user memberships if the field is not visualizationConfigs + # This is because the user can be requesting a visualizations from a parent + # node, that should be handling the filtering + if info.field_name != "visualizationConfigs": + return queryset + user_pk = getattr(info.context.user, "pk", False) - user_groups_ids = Membership.objects.filter( - user__id=user_pk, membership_status=Membership.APPROVED - ).values_list("group", flat=True) - user_landscape_ids = Landscape.objects.filter( - associated_groups__group__memberships__user__id=user_pk, - associated_groups__group__memberships__membership_status=Membership.APPROVED, - associated_groups__is_default_landscape_group=True, - ).values_list("id", flat=True) - all_ids = list(user_groups_ids) + list(user_landscape_ids) - return queryset.filter(data_entry__shared_resources__target_object_id__in=all_ids) + + user_groups_ids = Subquery( + Group.objects.filter( + memberships__user__id=user_pk, memberships__membership_status=Membership.APPROVED + ).values("id") + ) + user_landscape_ids = Subquery( + Landscape.objects.filter( + associated_groups__group__memberships__user__id=user_pk, + associated_groups__group__memberships__membership_status=Membership.APPROVED, + associated_groups__is_default_landscape_group=True, + ).values("id") + ) + return queryset.filter( + Q(data_entry__shared_resources__target_object_id__in=user_groups_ids) + | Q(data_entry__shared_resources__target_object_id__in=user_landscape_ids) + ) def resolve_mapbox_tileset_id(self, info): if self.mapbox_tileset_id is None: diff --git a/terraso_backend/apps/shared_data/models/data_entries.py b/terraso_backend/apps/shared_data/models/data_entries.py index 91811755b..3d63660d1 100644 --- a/terraso_backend/apps/shared_data/models/data_entries.py +++ b/terraso_backend/apps/shared_data/models/data_entries.py @@ -25,6 +25,8 @@ VALID_TARGET_TYPES = ["group", "landscape"] +data_entry_file_storage = DataEntryFileStorage(custom_domain=None) + class DataEntry(BaseModel): """ @@ -98,8 +100,7 @@ def s3_object_name(self): @property def signed_url(self): - storage = DataEntryFileStorage(custom_domain=None) - return storage.url(self.s3_object_name) + return data_entry_file_storage.url(self.s3_object_name) def delete_file_on_storage(self): if not self.deleted_at: diff --git a/terraso_backend/tests/graphql/test_visualization_config.py b/terraso_backend/tests/graphql/test_visualization_config.py index d03cbc317..ebca9bd55 100644 --- a/terraso_backend/tests/graphql/test_visualization_config.py +++ b/terraso_backend/tests/graphql/test_visualization_config.py @@ -160,7 +160,7 @@ def test_visualization_configs_filter_by_group_id_filters_successfuly( def test_visualization_configs_returns_only_for_users_groups( - client_query, visualization_config_current_user + client_query, visualization_config_current_user, visualization_config_other_user ): # It's being done a request for all configurations, but only the configurations # from logged user's group is expected to return. @@ -178,7 +178,8 @@ def test_visualization_configs_returns_only_for_users_groups( """ ) - edges = response.json()["data"]["visualizationConfigs"]["edges"] + json_response = response.json() + edges = json_response["data"]["visualizationConfigs"]["edges"] entries_result = [edge["node"]["id"] for edge in edges] assert len(entries_result) == 1 diff --git a/terraso_backend/tests/shared_data/test_views.py b/terraso_backend/tests/shared_data/test_views.py index 779baf03c..b3f2f9b1d 100644 --- a/terraso_backend/tests/shared_data/test_views.py +++ b/terraso_backend/tests/shared_data/test_views.py @@ -76,7 +76,6 @@ def test_create_data_entry_successfully( response = logged_client.post(upload_url, data_entry_payload) response_data = response.json() - print(response_data) assert response.status_code == 201 mocked_upload_service.assert_called_once()