From 68daa548609245688ed390e5ee794dca21d22f76 Mon Sep 17 00:00:00 2001 From: Henrique Pozzolini Date: Mon, 11 Nov 2024 21:31:39 -0300 Subject: [PATCH 1/6] BA-1779-be-multiple-profiles-member-list --- .../baseapp_profiles/graphql/object_types.py | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/baseapp-profiles/baseapp_profiles/graphql/object_types.py b/baseapp-profiles/baseapp_profiles/graphql/object_types.py index 9f39f558..24bf3bc4 100644 --- a/baseapp-profiles/baseapp_profiles/graphql/object_types.py +++ b/baseapp-profiles/baseapp_profiles/graphql/object_types.py @@ -9,7 +9,7 @@ ) from baseapp_pages.meta import AbstractMetadataObjectType from django.apps import apps -from django.db.models import Q +from django.db.models import Q, Case, When, Value, IntegerField from graphene import relay from graphene_django.filter import DjangoFilterConnectionField @@ -20,7 +20,6 @@ ProfileRoleTypesEnum = graphene.Enum.from_enum(ProfileUserRole.ProfileRoles) ProfileRoleStatusTypesEnum = graphene.Enum.from_enum(ProfileUserRole.ProfileRoleStatus) - class BaseProfileUserRoleObjectType: role = graphene.Field(ProfileRoleTypesEnum) status = graphene.Field(ProfileRoleStatusTypesEnum) @@ -94,8 +93,10 @@ class BaseProfileObjectType: target = graphene.Field(lambda: ProfileInterface) image = ThumbnailField(required=False) banner_image = ThumbnailField(required=False) - members = DjangoFilterConnectionField(get_object_type_for_model(ProfileUserRole)) - + members = DjangoFilterConnectionField( + get_object_type_for_model(ProfileUserRole), + order_by_status=graphene.String() + ) class Meta: interfaces = interfaces model = Profile @@ -126,10 +127,23 @@ def resolve_metadata(cls, instance, info): return ProfileMetadata(instance, info) @classmethod - def resolve_members(cls, instance, info, **kwargs): + def resolve_members(cls, instance, info, order_by_status=None, **kwargs): if not info.context.user.has_perm("baseapp_profiles.view_profile_members", instance): return instance.members.none() - return instance.members.all() + + members_queryset = instance.members.all() + + if order_by_status == "custom": + status_order = Case( + When(status=ProfileUserRole.ProfileRoleStatus.PENDING.value, then=Value(1)), + When(status=ProfileUserRole.ProfileRoleStatus.INACTIVE.value, then=Value(2)), + When(status=ProfileUserRole.ProfileRoleStatus.ACTIVE.value, then=Value(3)), + default=Value(4), + output_field=IntegerField() + ) + members_queryset = members_queryset.order_by(status_order) + + return members_queryset class ProfileObjectType(DjangoObjectType, BaseProfileObjectType): From ae2fea97c231b94600d95a2fbb7f8230c86f8962 Mon Sep 17 00:00:00 2001 From: Henrique Pozzolini Date: Tue, 12 Nov 2024 22:15:55 -0300 Subject: [PATCH 2/6] isort black and flake8 --- .../baseapp_profiles/graphql/object_types.py | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/baseapp-profiles/baseapp_profiles/graphql/object_types.py b/baseapp-profiles/baseapp_profiles/graphql/object_types.py index 24bf3bc4..67ab47d5 100644 --- a/baseapp-profiles/baseapp_profiles/graphql/object_types.py +++ b/baseapp-profiles/baseapp_profiles/graphql/object_types.py @@ -9,7 +9,7 @@ ) from baseapp_pages.meta import AbstractMetadataObjectType from django.apps import apps -from django.db.models import Q, Case, When, Value, IntegerField +from django.db.models import Case, IntegerField, Q, Value, When from graphene import relay from graphene_django.filter import DjangoFilterConnectionField @@ -20,6 +20,7 @@ ProfileRoleTypesEnum = graphene.Enum.from_enum(ProfileUserRole.ProfileRoles) ProfileRoleStatusTypesEnum = graphene.Enum.from_enum(ProfileUserRole.ProfileRoleStatus) + class BaseProfileUserRoleObjectType: role = graphene.Field(ProfileRoleTypesEnum) status = graphene.Field(ProfileRoleStatusTypesEnum) @@ -94,9 +95,9 @@ class BaseProfileObjectType: image = ThumbnailField(required=False) banner_image = ThumbnailField(required=False) members = DjangoFilterConnectionField( - get_object_type_for_model(ProfileUserRole), - order_by_status=graphene.String() + get_object_type_for_model(ProfileUserRole), order_by_status=graphene.String() ) + class Meta: interfaces = interfaces model = Profile @@ -132,15 +133,15 @@ def resolve_members(cls, instance, info, order_by_status=None, **kwargs): return instance.members.none() members_queryset = instance.members.all() - + if order_by_status == "custom": status_order = Case( - When(status=ProfileUserRole.ProfileRoleStatus.PENDING.value, then=Value(1)), - When(status=ProfileUserRole.ProfileRoleStatus.INACTIVE.value, then=Value(2)), - When(status=ProfileUserRole.ProfileRoleStatus.ACTIVE.value, then=Value(3)), - default=Value(4), - output_field=IntegerField() - ) + When(status=ProfileUserRole.ProfileRoleStatus.PENDING.value, then=Value(1)), + When(status=ProfileUserRole.ProfileRoleStatus.INACTIVE.value, then=Value(2)), + When(status=ProfileUserRole.ProfileRoleStatus.ACTIVE.value, then=Value(3)), + default=Value(4), + output_field=IntegerField(), + ) members_queryset = members_queryset.order_by(status_order) return members_queryset From bf8598e886adf15fd7c2aaf693e1f45f37218f83 Mon Sep 17 00:00:00 2001 From: Henrique Pozzolini Date: Mon, 2 Dec 2024 10:24:48 -0300 Subject: [PATCH 3/6] BA-1779-be-multiple-profiles-member-list --- baseapp-profiles/baseapp_profiles/graphql/__init__.py | 2 +- baseapp-profiles/baseapp_profiles/graphql/object_types.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/baseapp-profiles/baseapp_profiles/graphql/__init__.py b/baseapp-profiles/baseapp_profiles/graphql/__init__.py index 2c98b741..0e976e3a 100644 --- a/baseapp-profiles/baseapp_profiles/graphql/__init__.py +++ b/baseapp-profiles/baseapp_profiles/graphql/__init__.py @@ -1,3 +1,3 @@ # from .mutations import ProfileCreate, ProfileEdit # noqa -from .object_types import ProfileInterface # noqa +from .object_types import MemberInterface, ProfileInterface # noqa from .queries import ProfilesQueries # noqa diff --git a/baseapp-profiles/baseapp_profiles/graphql/object_types.py b/baseapp-profiles/baseapp_profiles/graphql/object_types.py index ab60d7cc..a0c0dbbd 100644 --- a/baseapp-profiles/baseapp_profiles/graphql/object_types.py +++ b/baseapp-profiles/baseapp_profiles/graphql/object_types.py @@ -22,13 +22,17 @@ ProfileRoleStatusTypesEnum = graphene.Enum.from_enum(ProfileUserRole.ProfileRoleStatus) +class MemberInterface(relay.Node): + member = graphene.Field(get_object_type_for_model(ProfileUserRole)) + + class BaseProfileUserRoleObjectType: role = graphene.Field(ProfileRoleTypesEnum) status = graphene.Field(ProfileRoleStatusTypesEnum) class Meta: model = ProfileUserRole - interfaces = [relay.Node] + interfaces = [MemberInterface] fields = ["id", "pk", "user", "role", "created", "modified", "status"] filter_fields = ["role"] From 01d35bdf672ee3dc23e99de70113d9649aa66284 Mon Sep 17 00:00:00 2001 From: Henrique Pozzolini Date: Tue, 3 Dec 2024 17:43:46 -0300 Subject: [PATCH 4/6] BA-1779-be-multiple-profiles-member-list --- .../baseapp_profiles/graphql/filters.py | 35 ++++++- .../baseapp_profiles/graphql/object_types.py | 24 ++--- .../tests/test_get_queries.py | 96 ++++++++++++++++++- baseapp-profiles/setup.cfg | 2 +- 4 files changed, 136 insertions(+), 21 deletions(-) diff --git a/baseapp-profiles/baseapp_profiles/graphql/filters.py b/baseapp-profiles/baseapp_profiles/graphql/filters.py index ef8da931..822fb917 100644 --- a/baseapp-profiles/baseapp_profiles/graphql/filters.py +++ b/baseapp-profiles/baseapp_profiles/graphql/filters.py @@ -1,8 +1,9 @@ import django_filters import swapper -from django.db.models import Q +from django.db.models import Case, IntegerField, Q, Value, When Profile = swapper.load_model("baseapp_profiles", "Profile") +ProfileUserRole = swapper.load_model("baseapp_profiles", "ProfileUserRole") class ProfileFilter(django_filters.FilterSet): @@ -22,3 +23,35 @@ class Meta: def filter_q(self, queryset, name, value): return queryset.filter(Q(name__icontains=value) | Q(url_paths__path__icontains=value)) + + +class MemberOrderingFilter(django_filters.OrderingFilter): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.extra["choices"] += [ + ("status", "Status"), + ] + + def filter(self, qs, value): + if value is None: + return qs + + if any(v == "status" for v in value): + status_order = Case( + When(status=ProfileUserRole.ProfileRoleStatus.PENDING.value, then=Value(1)), + When(status=ProfileUserRole.ProfileRoleStatus.INACTIVE.value, then=Value(2)), + When(status=ProfileUserRole.ProfileRoleStatus.ACTIVE.value, then=Value(3)), + default=Value(4), + output_field=IntegerField(), + ) + return qs.order_by(status_order) + + return super().filter(qs, value) + + +class MemberFilter(django_filters.FilterSet): + order_by = MemberOrderingFilter() + + class Meta: + model = ProfileUserRole + fields = ["role", "order_by"] diff --git a/baseapp-profiles/baseapp_profiles/graphql/object_types.py b/baseapp-profiles/baseapp_profiles/graphql/object_types.py index a0c0dbbd..33834cef 100644 --- a/baseapp-profiles/baseapp_profiles/graphql/object_types.py +++ b/baseapp-profiles/baseapp_profiles/graphql/object_types.py @@ -8,11 +8,11 @@ ) from baseapp_pages.meta import AbstractMetadataObjectType from django.apps import apps -from django.db.models import Case, IntegerField, Q, Value, When +from django.db.models import Q from graphene import relay from graphene_django.filter import DjangoFilterConnectionField -from .filters import ProfileFilter +from .filters import MemberFilter, ProfileFilter Profile = swapper.load_model("baseapp_profiles", "Profile") ProfileUserRole = swapper.load_model("baseapp_profiles", "ProfileUserRole") @@ -34,7 +34,7 @@ class Meta: model = ProfileUserRole interfaces = [MemberInterface] fields = ["id", "pk", "user", "role", "created", "modified", "status"] - filter_fields = ["role"] + filterset_class = MemberFilter class ProfileUserRoleObjectType(DjangoObjectType, BaseProfileUserRoleObjectType): @@ -94,7 +94,7 @@ class BaseProfileObjectType: image = ThumbnailField(required=False) banner_image = ThumbnailField(required=False) members = DjangoFilterConnectionField( - get_object_type_for_model(ProfileUserRole), order_by_status=graphene.String() + get_object_type_for_model(ProfileUserRole), ) class Meta: @@ -127,23 +127,11 @@ def resolve_metadata(cls, instance, info): return ProfileMetadata(instance, info) @classmethod - def resolve_members(cls, instance, info, order_by_status=None, **kwargs): + def resolve_members(cls, instance, info, **kwargs): if not info.context.user.has_perm("baseapp_profiles.view_profile_members", instance): return instance.members.none() - members_queryset = instance.members.all() - - if order_by_status == "custom": - status_order = Case( - When(status=ProfileUserRole.ProfileRoleStatus.PENDING.value, then=Value(1)), - When(status=ProfileUserRole.ProfileRoleStatus.INACTIVE.value, then=Value(2)), - When(status=ProfileUserRole.ProfileRoleStatus.ACTIVE.value, then=Value(3)), - default=Value(4), - output_field=IntegerField(), - ) - members_queryset = members_queryset.order_by(status_order) - - return members_queryset + return instance.members.all() class ProfileObjectType(DjangoObjectType, BaseProfileObjectType): diff --git a/baseapp-profiles/baseapp_profiles/tests/test_get_queries.py b/baseapp-profiles/baseapp_profiles/tests/test_get_queries.py index 1559a77e..c5a83526 100644 --- a/baseapp-profiles/baseapp_profiles/tests/test_get_queries.py +++ b/baseapp-profiles/baseapp_profiles/tests/test_get_queries.py @@ -3,7 +3,8 @@ from baseapp_pages.tests.factories import URLPathFactory from django.contrib.contenttypes.models import ContentType -from .factories import ProfileFactory +from ..models import ProfileUserRole +from .factories import ProfileFactory, ProfileUserRoleFactory pytestmark = pytest.mark.django_db @@ -144,6 +145,99 @@ def test_another_user_cant_view_members(graphql_user_client): assert content["data"]["profile"]["members"] +def test_members_ordered_by_status(django_user_client, graphql_user_client): + # Cria um perfil com membros com diferentes status + user = django_user_client.user + profile = ProfileFactory(owner=user) + ProfileUserRoleFactory( + profile=profile, + status=ProfileUserRole.ProfileRoleStatus.ACTIVE, + ) + ProfileUserRoleFactory( + profile=profile, + status=ProfileUserRole.ProfileRoleStatus.PENDING, + ) + ProfileUserRoleFactory( + profile=profile, + status=ProfileUserRole.ProfileRoleStatus.ACTIVE, + ) + ProfileUserRoleFactory( + profile=profile, + status=ProfileUserRole.ProfileRoleStatus.INACTIVE, + ) + + response = graphql_user_client( + query=""" + query Profile($id: ID!, $orderBy: String) { + profile(id: $id) { + members(orderBy: $orderBy) { + edges { + node { + id + status + } + } + } + } + } + """, + variables={"id": profile.relay_id, "orderBy": "status"}, + ) + + content = response.json() + + members = content["data"]["profile"]["members"]["edges"] + statuses = [member["node"]["status"] for member in members] + + assert statuses == ["PENDING", "INACTIVE", "ACTIVE", "ACTIVE"] + + +def test_members_not_ordered_by_status(django_user_client, graphql_user_client): + user = django_user_client.user + profile = ProfileFactory(owner=user) + ProfileUserRoleFactory( + profile=profile, + status=ProfileUserRole.ProfileRoleStatus.ACTIVE, + ) + ProfileUserRoleFactory( + profile=profile, + status=ProfileUserRole.ProfileRoleStatus.PENDING, + ) + ProfileUserRoleFactory( + profile=profile, + status=ProfileUserRole.ProfileRoleStatus.ACTIVE, + ) + ProfileUserRoleFactory( + profile=profile, + status=ProfileUserRole.ProfileRoleStatus.INACTIVE, + ) + + response = graphql_user_client( + query=""" + query Profile($id: ID!) { + profile(id: $id) { + members { + edges { + node { + id + status + } + } + } + } + } + """, + variables={"id": profile.relay_id}, + ) + + content = response.json() + + members = content["data"]["profile"]["members"]["edges"] + statuses = [member["node"]["status"] for member in members] + + assert statuses == ["ACTIVE", "PENDING", "ACTIVE", "INACTIVE"] + + def test_search_profiles(graphql_user_client): profile1 = ProfileFactory(name="David") profile2 = ProfileFactory(name="Daniel") diff --git a/baseapp-profiles/setup.cfg b/baseapp-profiles/setup.cfg index 9f224bea..5a69b252 100644 --- a/baseapp-profiles/setup.cfg +++ b/baseapp-profiles/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = baseapp_profiles -version = 0.3.1 +version = 0.3.2 description = BaseApp Profiles long_description = file: README.md long_description_content_type = text/markdown From a02e4735b5c19ac30ed0f331ff291a764e1103f4 Mon Sep 17 00:00:00 2001 From: Henrique Pozzolini Date: Wed, 4 Dec 2024 10:06:44 -0300 Subject: [PATCH 5/6] remove comment --- baseapp-profiles/baseapp_profiles/tests/test_get_queries.py | 1 - 1 file changed, 1 deletion(-) diff --git a/baseapp-profiles/baseapp_profiles/tests/test_get_queries.py b/baseapp-profiles/baseapp_profiles/tests/test_get_queries.py index c5a83526..d784da00 100644 --- a/baseapp-profiles/baseapp_profiles/tests/test_get_queries.py +++ b/baseapp-profiles/baseapp_profiles/tests/test_get_queries.py @@ -146,7 +146,6 @@ def test_another_user_cant_view_members(graphql_user_client): def test_members_ordered_by_status(django_user_client, graphql_user_client): - # Cria um perfil com membros com diferentes status user = django_user_client.user profile = ProfileFactory(owner=user) ProfileUserRoleFactory( From 2f784360be3cd060353da8fc0fd6293cf25d878f Mon Sep 17 00:00:00 2001 From: Henrique Pozzolini Date: Wed, 4 Dec 2024 10:41:08 -0300 Subject: [PATCH 6/6] remove MemberInterface --- baseapp-profiles/baseapp_profiles/graphql/__init__.py | 2 +- baseapp-profiles/baseapp_profiles/graphql/object_types.py | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/baseapp-profiles/baseapp_profiles/graphql/__init__.py b/baseapp-profiles/baseapp_profiles/graphql/__init__.py index 0e976e3a..2c98b741 100644 --- a/baseapp-profiles/baseapp_profiles/graphql/__init__.py +++ b/baseapp-profiles/baseapp_profiles/graphql/__init__.py @@ -1,3 +1,3 @@ # from .mutations import ProfileCreate, ProfileEdit # noqa -from .object_types import MemberInterface, ProfileInterface # noqa +from .object_types import ProfileInterface # noqa from .queries import ProfilesQueries # noqa diff --git a/baseapp-profiles/baseapp_profiles/graphql/object_types.py b/baseapp-profiles/baseapp_profiles/graphql/object_types.py index 33834cef..eaed59e2 100644 --- a/baseapp-profiles/baseapp_profiles/graphql/object_types.py +++ b/baseapp-profiles/baseapp_profiles/graphql/object_types.py @@ -22,17 +22,13 @@ ProfileRoleStatusTypesEnum = graphene.Enum.from_enum(ProfileUserRole.ProfileRoleStatus) -class MemberInterface(relay.Node): - member = graphene.Field(get_object_type_for_model(ProfileUserRole)) - - class BaseProfileUserRoleObjectType: role = graphene.Field(ProfileRoleTypesEnum) status = graphene.Field(ProfileRoleStatusTypesEnum) class Meta: model = ProfileUserRole - interfaces = [MemberInterface] + interfaces = [relay.Node] fields = ["id", "pk", "user", "role", "created", "modified", "status"] filterset_class = MemberFilter