diff --git a/terraso_backend/apps/graphql/schema/groups.py b/terraso_backend/apps/graphql/schema/groups.py index 6b1f79971..940e2cd67 100644 --- a/terraso_backend/apps/graphql/schema/groups.py +++ b/terraso_backend/apps/graphql/schema/groups.py @@ -1,3 +1,4 @@ +import django_filters import graphene from django.conf import settings from graphene import relay @@ -9,24 +10,40 @@ from .commons import BaseDeleteMutation, BaseWriteMutation, TerrasoConnection -class GroupNode(DjangoObjectType): - id = graphene.ID(source="pk", required=True) +class GroupFilterSet(django_filters.FilterSet): + # TODO: members__email was kept for backward compatibility. Remove as soon + # as the web client be updated to use memberships__email filter + members__email = django_filters.CharFilter(method="filter_memberships_email") + memberships__email = django_filters.CharFilter(method="filter_memberships_email") + associated_landscapes__is_default_landscape_group = django_filters.BooleanFilter( + method="filter_associated_landscapes" + ) + associated_landscapes__isnull = django_filters.BooleanFilter( + method="filter_associated_landscapes" + ) class Meta: model = Group - filter_fields = { + fields = { "name": ["exact", "icontains", "istartswith"], "slug": ["exact", "icontains"], "description": ["icontains"], - "associations_as_parent__child_group": ["exact"], - "associations_as_child__parent_group": ["exact"], - "associations_as_parent__child_group__slug": ["icontains"], - "associations_as_child__parent_group__slug": ["icontains"], - "memberships": ["exact"], - "associated_landscapes__is_default_landscape_group": ["exact"], - "associated_landscapes": ["isnull"], - "members__email": ["exact"], } + + def filter_memberships_email(self, queryset, name, value): + return queryset.filter(memberships__user__email=value, memberships__deleted_at__isnull=True) + + def filter_associated_landscapes(self, queryset, name, value): + filters = {"associated_landscapes__deleted_at__isnull": True} + filters[name] = value + return queryset.filter(**filters) + + +class GroupNode(DjangoObjectType): + id = graphene.ID(source="pk", required=True) + + class Meta: + model = Group fields = ( "name", "slug", @@ -39,6 +56,7 @@ class Meta: "associations_as_child", "associated_landscapes", ) + filterset_class = GroupFilterSet interfaces = (relay.Node,) connection_class = TerrasoConnection @@ -102,8 +120,6 @@ def mutate_and_get_payload(cls, root, info, **kwargs): ff_check_permission_on = settings.FEATURE_FLAGS["CHECK_PERMISSIONS"] user_has_delete_permission = user.has_perm(Group.get_perm("delete"), obj=kwargs["id"]) - if ff_check_permission_on and not user_has_delete_permission: raise GraphQLValidationException("User has no permission to delete this data.") - return super().mutate_and_get_payload(root, info, **kwargs) diff --git a/terraso_backend/tests/graphql/test_groups_filters.py b/terraso_backend/tests/graphql/test_groups_filters.py new file mode 100644 index 000000000..fd9c4d3e8 --- /dev/null +++ b/terraso_backend/tests/graphql/test_groups_filters.py @@ -0,0 +1,141 @@ +import pytest +from mixer.backend.django import mixer + +from apps.core.models import Group, Membership + +pytestmark = pytest.mark.django_db + + +def test_groups_filter_by_membership_user_ignores_deleted_memberships(client_query, memberships): + membership = memberships[0] + membership.delete() + + response = client_query( + """ + {groups(memberships_Email: "%s") { + edges { + node { + slug + } + } + }} + """ + % membership.user.email + ) + edges = response.json()["data"]["groups"]["edges"] + + assert not edges + + +def test_groups_filter_by_with_landscape_association(client_query, users, landscape_groups): + user = users[0] + default_group_association, common_group_association = landscape_groups + mixer.blend(Membership, user=user, group=default_group_association.group) + mixer.blend(Membership, user=user, group=common_group_association.group) + + response = client_query( + """ + {groups( + associatedLandscapes_Isnull: false, + memberships_Email: "%s" + ) { + edges { + node { + slug + } + } + }} + """ + % user.email + ) + edges = response.json()["data"]["groups"]["edges"] + groups_result = [edge["node"]["slug"] for edge in edges] + + assert len(groups_result) == 2 + assert default_group_association.group.slug in groups_result + assert common_group_association.group.slug in groups_result + + +def test_groups_filter_by_with_landscape_association_ignores_deleted_associations( + client_query, users, landscape_groups +): + user = users[0] + default_group_association, common_group_association = landscape_groups + mixer.blend(Membership, user=user, group=default_group_association.group) + mixer.blend(Membership, user=user, group=common_group_association.group) + + default_group_association.delete() + + response = client_query( + """ + {groups( + associatedLandscapes_Isnull: false, + memberships_Email: "%s" + ) { + edges { + node { + slug + } + } + }} + """ + % user.email + ) + edges = response.json()["data"]["groups"]["edges"] + groups_result = [edge["node"]["slug"] for edge in edges] + + assert len(groups_result) == 1 + + +def test_groups_filter_by_default_landscape_group(client_query, users, landscape_groups): + user = users[0] + default_group_association, common_group_association = landscape_groups + mixer.blend(Membership, user=user, group=default_group_association.group) + mixer.blend(Membership, user=user, group=common_group_association.group) + + response = client_query( + """ + {groups( + associatedLandscapes_IsDefaultLandscapeGroup: true, + memberships_Email: "%s" + ) { + edges { + node { + slug + } + } + }} + """ + % user.email + ) + edges = response.json()["data"]["groups"]["edges"] + groups_result = [edge["node"]["slug"] for edge in edges] + + assert len(groups_result) == 1 + assert default_group_association.group.slug in groups_result + + +def test_groups_filter_by_without_landscape_association(client_query, users): + user = users[0] + group = mixer.blend(Group) + membership = mixer.blend(Membership, user=user, group=group) + + response = client_query( + """ + {groups( + associatedLandscapes_Isnull: true, + memberships_Email: "%s" + ) { + edges { + node { + slug + } + } + }} + """ + % membership.user.email + ) + edges = response.json()["data"]["groups"]["edges"] + groups_result = [edge["node"]["slug"] for edge in edges] + assert len(groups_result) == 1 + assert groups_result[0] == group.slug